nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (420) hide show
  1. nautobot/apps/choices.py +0 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +9 -1
  5. nautobot/circuits/filters.py +3 -2
  6. nautobot/circuits/navigation.py +3 -2
  7. nautobot/circuits/templates/circuits/circuit.html +1 -1
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination.html +1 -1
  10. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  11. nautobot/circuits/templates/circuits/circuittype.html +1 -1
  12. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  13. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  14. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  15. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  16. nautobot/cloud/filters.py +1 -1
  17. nautobot/cloud/navigation.py +3 -2
  18. nautobot/core/api/schema.py +1 -1
  19. nautobot/core/api/serializers.py +6 -1
  20. nautobot/core/api/urls.py +1 -0
  21. nautobot/core/api/views.py +8 -0
  22. nautobot/core/apps/__init__.py +11 -10
  23. nautobot/core/celery/__init__.py +3 -5
  24. nautobot/core/checks.py +46 -0
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
  26. nautobot/core/cli/migrate_deprecated_templates.py +200 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/forms/forms.py +1 -1
  30. nautobot/core/jobs/__init__.py +6 -3
  31. nautobot/core/jobs/groups.py +31 -1
  32. nautobot/core/management/commands/generate_test_data.py +28 -9
  33. nautobot/core/models/generics.py +9 -1
  34. nautobot/core/models/tree_queries.py +10 -5
  35. nautobot/core/settings.py +18 -12
  36. nautobot/core/settings.yaml +13 -7
  37. nautobot/core/signals.py +12 -1
  38. nautobot/core/tables.py +13 -6
  39. nautobot/core/templates/40x.html +1 -1
  40. nautobot/core/templates/500.html +2 -2
  41. nautobot/core/templates/admin/config/config.html +12 -12
  42. nautobot/core/templates/admin/index.html +3 -3
  43. nautobot/core/templates/buttons/export.html +1 -1
  44. nautobot/core/templates/components/button/dropdown.html +5 -3
  45. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  46. nautobot/core/templates/components/panel/panel.html +3 -3
  47. nautobot/core/templates/components/tab/content_wrapper.html +2 -3
  48. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  49. nautobot/core/templates/echarts/echarts.html +1 -1
  50. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  51. nautobot/core/templates/generic/object_bulk_create.html +4 -3
  52. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  53. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  54. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  55. nautobot/core/templates/generic/object_create.html +5 -4
  56. nautobot/core/templates/generic/object_import.html +2 -1
  57. nautobot/core/templates/generic/object_list.html +12 -4
  58. nautobot/core/templates/generic/object_notes.html +5 -3
  59. nautobot/core/templates/generic/object_retrieve.html +2 -3
  60. nautobot/core/templates/graphene/graphiql.html +7 -7
  61. nautobot/core/templates/home.html +1 -1
  62. nautobot/core/templates/import_success.html +2 -1
  63. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  64. nautobot/core/templates/inc/created_updated.html +7 -3
  65. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  66. nautobot/core/templates/inc/form_static_field.html +6 -0
  67. nautobot/core/templates/inc/header.html +1 -1
  68. nautobot/core/templates/inc/image_attachments.html +2 -1
  69. nautobot/core/templates/inc/nav_menu.html +2 -1
  70. nautobot/core/templates/inc/search_panel.html +4 -4
  71. nautobot/core/templates/login.html +4 -2
  72. nautobot/core/templates/nautobot_config.py.j2 +6 -5
  73. nautobot/core/templates/redoc_ui.html +7 -0
  74. nautobot/core/templates/search.html +1 -1
  75. nautobot/core/templates/swagger_ui.html +17 -3
  76. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  77. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  78. nautobot/core/templates/utilities/obj_table.html +10 -2
  79. nautobot/core/templates/utilities/render_field.html +7 -7
  80. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  81. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +4 -4
  82. nautobot/core/templates/utilities/theme_preview.html +16 -3
  83. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  84. nautobot/core/templatetags/helpers.py +52 -6
  85. nautobot/core/testing/api.py +68 -9
  86. nautobot/core/testing/filters.py +0 -23
  87. nautobot/core/testing/integration.py +23 -10
  88. nautobot/core/testing/mixins.py +2 -0
  89. nautobot/core/testing/views.py +4 -0
  90. nautobot/core/tests/integration/test_app_home.py +34 -30
  91. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  92. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  93. nautobot/core/tests/runner.py +9 -1
  94. nautobot/core/tests/test_api.py +5 -3
  95. nautobot/core/tests/test_breadcrumbs.py +6 -7
  96. nautobot/core/tests/test_checks.py +28 -0
  97. nautobot/core/tests/test_cli.py +40 -0
  98. nautobot/core/tests/test_config.py +2 -1
  99. nautobot/core/tests/test_forms.py +55 -13
  100. nautobot/core/tests/test_jobs.py +75 -1
  101. nautobot/core/tests/test_nautobot_server.py +2 -0
  102. nautobot/core/tests/test_navigations.py +76 -1
  103. nautobot/core/tests/test_patch_social_django.py +42 -0
  104. nautobot/core/tests/test_tables.py +3 -1
  105. nautobot/core/tests/test_templatetags_helpers.py +53 -13
  106. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  107. nautobot/core/tests/test_tree_queries.py +14 -1
  108. nautobot/core/tests/test_ui.py +1 -1
  109. nautobot/core/tests/test_utils.py +31 -4
  110. nautobot/core/tests/test_views.py +159 -31
  111. nautobot/core/ui/breadcrumbs.py +2 -12
  112. nautobot/core/ui/choices.py +142 -10
  113. nautobot/core/ui/constants.py +76 -12
  114. nautobot/core/ui/object_detail.py +92 -12
  115. nautobot/core/urls.py +12 -1
  116. nautobot/core/utils/cache.py +2 -1
  117. nautobot/core/utils/filtering.py +17 -17
  118. nautobot/core/utils/lookup.py +3 -8
  119. nautobot/core/utils/module_loading.py +21 -0
  120. nautobot/core/utils/patch_social_django.py +128 -0
  121. nautobot/core/views/__init__.py +38 -1
  122. nautobot/core/views/generic.py +3 -3
  123. nautobot/core/views/mixins.py +15 -3
  124. nautobot/core/views/renderers.py +2 -0
  125. nautobot/core/views/viewsets.py +2 -1
  126. nautobot/data_validation/apps.py +1 -5
  127. nautobot/data_validation/custom_validators.py +4 -4
  128. nautobot/data_validation/filters.py +1 -1
  129. nautobot/data_validation/forms.py +40 -0
  130. nautobot/data_validation/migrations/0001_initial.py +0 -7
  131. nautobot/data_validation/migrations/0002_data_migration_from_app.py +0 -12
  132. nautobot/data_validation/models.py +16 -7
  133. nautobot/data_validation/navigation.py +8 -1
  134. nautobot/data_validation/tables.py +12 -5
  135. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  136. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  137. nautobot/data_validation/tests/__init__.py +2 -2
  138. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  139. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  140. nautobot/data_validation/tests/test_filters.py +8 -6
  141. nautobot/data_validation/tests/test_models.py +15 -0
  142. nautobot/data_validation/tests/test_views.py +190 -32
  143. nautobot/data_validation/urls.py +2 -5
  144. nautobot/data_validation/views.py +73 -40
  145. nautobot/dcim/api/serializers.py +0 -13
  146. nautobot/dcim/apps.py +4 -0
  147. nautobot/dcim/choices.py +16 -0
  148. nautobot/dcim/custom_validators.py +84 -0
  149. nautobot/dcim/filter_mixins.py +353 -4
  150. nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
  151. nautobot/dcim/forms.py +1 -1
  152. nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
  153. nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
  154. nautobot/dcim/models/device_components.py +81 -68
  155. nautobot/dcim/models/devices.py +13 -16
  156. nautobot/dcim/navigation.py +7 -6
  157. nautobot/dcim/tables/devices.py +3 -0
  158. nautobot/dcim/tables/template_code.py +14 -14
  159. nautobot/dcim/templates/dcim/cable.html +2 -61
  160. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  161. nautobot/dcim/templates/dcim/cable_edit.html +2 -5
  162. nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
  163. nautobot/dcim/templates/dcim/cable_trace.html +1 -3
  164. nautobot/dcim/templates/dcim/cable_update.html +5 -0
  165. nautobot/dcim/templates/dcim/consoleport.html +6 -5
  166. nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
  167. nautobot/dcim/templates/dcim/device/config.html +2 -2
  168. nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
  169. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
  170. nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
  171. nautobot/dcim/templates/dcim/device/frontports.html +1 -1
  172. nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
  173. nautobot/dcim/templates/dcim/device/inventory.html +1 -1
  174. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  175. nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
  176. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
  177. nautobot/dcim/templates/dcim/device/powerports.html +1 -1
  178. nautobot/dcim/templates/dcim/device/rearports.html +1 -1
  179. nautobot/dcim/templates/dcim/device/status.html +8 -8
  180. nautobot/dcim/templates/dcim/device/wireless.html +1 -1
  181. nautobot/dcim/templates/dcim/device.html +1 -1
  182. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  183. nautobot/dcim/templates/dcim/device_create.html +5 -3
  184. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  185. nautobot/dcim/templates/dcim/device_list.html +73 -10
  186. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  187. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  188. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  189. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  190. nautobot/dcim/templates/dcim/frontport.html +9 -8
  191. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  192. nautobot/dcim/templates/dcim/interface.html +26 -6
  193. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  194. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  195. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  196. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  197. nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
  198. nautobot/dcim/templates/dcim/module/base.html +49 -9
  199. nautobot/dcim/templates/dcim/module_list.html +57 -8
  200. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  201. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  202. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  203. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  204. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  205. nautobot/dcim/templates/dcim/powerport.html +5 -4
  206. nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
  207. nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
  208. nautobot/dcim/templates/dcim/rearport.html +7 -6
  209. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  210. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  211. nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
  212. nautobot/dcim/tests/integration/test_controller.py +1 -0
  213. nautobot/dcim/tests/test_api.py +8 -0
  214. nautobot/dcim/tests/test_custom_validators.py +229 -0
  215. nautobot/dcim/tests/test_filters.py +12 -6
  216. nautobot/dcim/tests/test_models.py +63 -4
  217. nautobot/dcim/tests/test_views.py +63 -22
  218. nautobot/dcim/urls.py +64 -21
  219. nautobot/dcim/utils.py +3 -3
  220. nautobot/dcim/views.py +547 -273
  221. nautobot/extras/api/views.py +9 -1
  222. nautobot/extras/choices.py +2 -13
  223. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  224. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  225. nautobot/extras/{filters/__init__.py → filters.py} +14 -46
  226. nautobot/extras/forms/forms.py +5 -13
  227. nautobot/extras/forms/mixins.py +0 -41
  228. nautobot/extras/management/__init__.py +9 -0
  229. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  230. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  231. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  232. nautobot/extras/models/__init__.py +1 -2
  233. nautobot/extras/models/approvals.py +22 -13
  234. nautobot/extras/models/contacts.py +2 -0
  235. nautobot/extras/models/groups.py +44 -5
  236. nautobot/extras/models/jobs.py +59 -1
  237. nautobot/extras/models/mixins.py +28 -0
  238. nautobot/extras/models/models.py +13 -0
  239. nautobot/extras/models/secrets.py +1 -0
  240. nautobot/extras/models/statuses.py +0 -15
  241. nautobot/extras/navigation.py +13 -9
  242. nautobot/extras/plugins/__init__.py +33 -55
  243. nautobot/extras/plugins/tables.py +3 -3
  244. nautobot/extras/plugins/urls.py +2 -21
  245. nautobot/extras/plugins/utils.py +1 -33
  246. nautobot/extras/plugins/views.py +0 -4
  247. nautobot/extras/signals.py +20 -19
  248. nautobot/extras/tables.py +52 -68
  249. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  250. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  251. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  252. nautobot/extras/templates/extras/computedfield.html +1 -1
  253. nautobot/extras/templates/extras/configcontext.html +1 -1
  254. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  255. nautobot/extras/templates/extras/customfield.html +1 -1
  256. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  257. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  258. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  259. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
  260. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  261. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  262. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  263. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  264. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  265. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  266. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  267. nautobot/extras/templates/extras/job.html +29 -12
  268. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  269. nautobot/extras/templates/extras/job_edit.html +52 -46
  270. nautobot/extras/templates/extras/job_list.html +29 -25
  271. nautobot/extras/templates/extras/marketplace.html +5 -9
  272. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  273. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  274. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -37
  275. nautobot/extras/templates/extras/plugin_detail.html +26 -21
  276. nautobot/extras/templates/extras/plugins_list.html +16 -26
  277. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  278. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  279. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  280. nautobot/extras/templates/extras/tag.html +1 -1
  281. nautobot/extras/templatetags/custom_links.py +12 -12
  282. nautobot/extras/templatetags/job_buttons.py +14 -12
  283. nautobot/extras/test_jobs/invalid_import.py +9 -0
  284. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  285. nautobot/extras/test_jobs/missing_import.py +11 -0
  286. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  287. nautobot/extras/tests/integration/test_customfields.py +8 -7
  288. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
  289. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  290. nautobot/extras/tests/integration/test_plugins.py +18 -6
  291. nautobot/extras/tests/test_api.py +27 -18
  292. nautobot/extras/tests/test_approvals.py +38 -38
  293. nautobot/extras/tests/test_changelog.py +35 -3
  294. nautobot/extras/tests/test_customfields.py +22 -13
  295. nautobot/extras/tests/test_customfields_filters.py +479 -0
  296. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  297. nautobot/extras/tests/test_filters.py +21 -19
  298. nautobot/extras/tests/test_forms.py +18 -21
  299. nautobot/extras/tests/test_jobs.py +25 -4
  300. nautobot/extras/tests/test_migrations.py +1 -0
  301. nautobot/extras/tests/test_models.py +13 -31
  302. nautobot/extras/tests/test_plugins.py +36 -10
  303. nautobot/extras/tests/test_views.py +31 -30
  304. nautobot/extras/views.py +81 -19
  305. nautobot/ipam/factory.py +7 -0
  306. nautobot/ipam/filter_mixins.py +38 -0
  307. nautobot/ipam/filters.py +27 -38
  308. nautobot/ipam/formfields.py +1 -1
  309. nautobot/ipam/forms.py +6 -3
  310. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  311. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  312. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  313. nautobot/ipam/models.py +29 -2
  314. nautobot/ipam/navigation.py +3 -2
  315. nautobot/ipam/signals.py +71 -0
  316. nautobot/ipam/tables.py +13 -6
  317. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  318. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  319. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  320. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  321. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  322. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  323. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  324. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  325. nautobot/ipam/templates/ipam/service.html +1 -1
  326. nautobot/ipam/templates/ipam/vlan.html +1 -1
  327. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  328. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  329. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  330. nautobot/ipam/tests/test_api.py +13 -6
  331. nautobot/ipam/tests/test_filters.py +10 -0
  332. nautobot/ipam/tests/test_forms.py +1 -1
  333. nautobot/ipam/tests/test_models.py +43 -1
  334. nautobot/ipam/tests/test_tables.py +1 -2
  335. nautobot/ipam/tests/test_utils.py +1 -1
  336. nautobot/ipam/tests/test_views.py +13 -14
  337. nautobot/ipam/ui.py +0 -17
  338. nautobot/ipam/utils/migrations.py +16 -2
  339. nautobot/ipam/utils/testing.py +9 -3
  340. nautobot/ipam/views.py +46 -6
  341. nautobot/project-static/dist/css/nautobot.css +1 -1
  342. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  343. nautobot/project-static/dist/js/nautobot.js +1 -1
  344. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  345. nautobot/project-static/js/cabletrace.js +1 -1
  346. nautobot/project-static/js/interface_filtering.js +20 -16
  347. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  348. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  349. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  350. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  351. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  352. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  353. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  354. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  355. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  356. nautobot/project-static/nautobot-icons/security.svg +3 -0
  357. nautobot/project-static/nautobot-icons/server.svg +1 -1
  358. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  359. nautobot/project-static/nautobot-icons/star.svg +1 -1
  360. nautobot/tenancy/api/serializers.py +1 -0
  361. nautobot/tenancy/api/views.py +2 -1
  362. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  363. nautobot/tenancy/navigation.py +3 -1
  364. nautobot/tenancy/tests/test_filters.py +0 -2
  365. nautobot/tenancy/views.py +2 -1
  366. nautobot/ui/src/js/collapse.js +3 -3
  367. nautobot/ui/src/js/nautobot.js +16 -0
  368. nautobot/ui/src/scss/colors.scss +1 -1
  369. nautobot/ui/src/scss/nautobot.scss +61 -28
  370. nautobot/users/templates/users/profile.html +45 -12
  371. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  372. nautobot/users/tests/test_api.py +4 -0
  373. nautobot/users/views.py +4 -2
  374. nautobot/virtualization/models.py +1 -68
  375. nautobot/virtualization/navigation.py +3 -2
  376. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  377. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  378. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  379. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  380. nautobot/virtualization/tests/test_api.py +3 -0
  381. nautobot/virtualization/tests/test_models.py +44 -4
  382. nautobot/vpn/__init__.py +0 -0
  383. nautobot/vpn/api/serializers.py +113 -0
  384. nautobot/vpn/api/urls.py +19 -0
  385. nautobot/vpn/api/views.py +70 -0
  386. nautobot/vpn/apps.py +8 -0
  387. nautobot/vpn/choices.py +171 -0
  388. nautobot/vpn/factory.py +209 -0
  389. nautobot/vpn/filters.py +233 -0
  390. nautobot/vpn/forms.py +486 -0
  391. nautobot/vpn/homepage.py +19 -0
  392. nautobot/vpn/migrations/0001_initial.py +541 -0
  393. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  394. nautobot/vpn/migrations/__init__.py +0 -0
  395. nautobot/vpn/models.py +527 -0
  396. nautobot/vpn/navigation.py +98 -0
  397. nautobot/vpn/tables.py +380 -0
  398. nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
  399. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  400. nautobot/vpn/tests/__init__.py +0 -0
  401. nautobot/vpn/tests/test_api.py +341 -0
  402. nautobot/vpn/tests/test_filters.py +139 -0
  403. nautobot/vpn/tests/test_forms.py +294 -0
  404. nautobot/vpn/tests/test_models.py +97 -0
  405. nautobot/vpn/tests/test_views.py +281 -0
  406. nautobot/vpn/urls.py +16 -0
  407. nautobot/vpn/views.py +437 -0
  408. nautobot/wireless/navigation.py +3 -2
  409. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  410. nautobot/wireless/tests/test_api.py +1 -1
  411. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +14 -14
  412. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
  413. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
  414. nautobot/data_validation/template_content.py +0 -42
  415. nautobot/dcim/filters/mixins.py +0 -354
  416. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  417. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  418. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
  419. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
  420. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
1
  from nautobot.core.apps import NautobotConfig
2
- from nautobot.extras.plugins import register_custom_validators, register_template_extensions
2
+ from nautobot.extras.plugins import register_custom_validators
3
3
 
4
4
 
5
5
  class DataValidationEngineConfig(NautobotConfig):
@@ -15,8 +15,4 @@ class DataValidationEngineConfig(NautobotConfig):
15
15
 
16
16
  register_custom_validators(custom_validators)
17
17
 
18
- from nautobot.data_validation.template_content import template_extensions
19
-
20
- register_template_extensions(template_extensions)
21
-
22
18
  import nautobot.data_validation.signals # noqa: F401 # unused-import -- but this import installs the signals
@@ -1,7 +1,7 @@
1
1
  """
2
2
  This is the meat of this app.
3
3
 
4
- Here we dynamically generate a PluginCustomValidator class
4
+ Here we dynamically generate a CustomValidator class
5
5
  for each model currently registered in the extras_features
6
6
  query registry 'custom_validators'.
7
7
 
@@ -40,7 +40,7 @@ LOGGER = logging.getLogger(__name__)
40
40
 
41
41
 
42
42
  class BaseValidator(CustomValidator):
43
- """Base PluginCustomValidator class that implements the core logic for enforcing validation rules defined in this app."""
43
+ """Base CustomValidator class that implements the core logic for enforcing validation rules defined in this app."""
44
44
 
45
45
  model = None
46
46
 
@@ -314,10 +314,10 @@ class DataComplianceRule(CustomValidator):
314
314
 
315
315
 
316
316
  class CustomValidatorIterator:
317
- """Iterator that generates PluginCustomValidator classes for each model registered in the extras feature query registry 'custom_validators'."""
317
+ """Iterator that generates CustomValidator classes for each model registered in the extras feature query registry 'custom_validators'."""
318
318
 
319
319
  def __iter__(self):
320
- """Return a generator of PluginCustomValidator classes for each registered model."""
320
+ """Return a generator of CustomValidator classes for each registered model."""
321
321
  for app_label, models in registry["model_features"]["custom_validators"].items():
322
322
  for model in models:
323
323
  yield type(
@@ -1,6 +1,5 @@
1
1
  """Filtering for data_validation."""
2
2
 
3
- from nautobot.apps.filters import NautobotFilterSet
4
3
  from nautobot.core.filters import ContentTypeMultipleChoiceFilter, SearchFilter
5
4
  from nautobot.data_validation.models import (
6
5
  DataCompliance,
@@ -9,6 +8,7 @@ from nautobot.data_validation.models import (
9
8
  RequiredValidationRule,
10
9
  UniqueValidationRule,
11
10
  )
11
+ from nautobot.extras.filters import NautobotFilterSet
12
12
  from nautobot.extras.utils import FeatureQuery
13
13
 
14
14
 
@@ -15,6 +15,7 @@ from nautobot.core.forms import (
15
15
  TagFilterField,
16
16
  )
17
17
  from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
18
+ from nautobot.core.utils.config import get_settings_or_config
18
19
  from nautobot.data_validation.models import (
19
20
  DataCompliance,
20
21
  MinMaxValidationRule,
@@ -22,6 +23,8 @@ from nautobot.data_validation.models import (
22
23
  RequiredValidationRule,
23
24
  UniqueValidationRule,
24
25
  )
26
+ from nautobot.dcim.choices import DeviceUniquenessChoices
27
+ from nautobot.dcim.models import Device
25
28
  from nautobot.extras.forms import (
26
29
  NautobotBulkEditForm,
27
30
  NautobotFilterForm,
@@ -300,3 +303,40 @@ class DataComplianceFilterForm(BootstrapMixin, forms.Form):
300
303
  required=False,
301
304
  )
302
305
  q = forms.CharField(required=False, label="Search")
306
+
307
+
308
+ #
309
+ # Device Constraints
310
+ #
311
+
312
+
313
+ class DeviceConstraintsForm(BootstrapMixin, forms.Form):
314
+ DEVICE_UNIQUENESS = forms.ChoiceField(
315
+ choices=DeviceUniquenessChoices.CHOICES,
316
+ label="Device Uniqueness",
317
+ required=True,
318
+ error_messages={
319
+ "invalid_choice": f"Invalid value. Available options are: {', '.join(DeviceUniquenessChoices.values())}"
320
+ },
321
+ )
322
+ DEVICE_NAME_REQUIRED = forms.BooleanField(
323
+ label="Device name required (cannot be blank or null)",
324
+ initial=False,
325
+ required=False,
326
+ )
327
+
328
+ def __init__(self, *args, user=None, **kwargs):
329
+ super().__init__(*args, **kwargs)
330
+
331
+ self.fields["DEVICE_UNIQUENESS"].initial = get_settings_or_config(
332
+ "DEVICE_UNIQUENESS", fallback=DeviceUniquenessChoices.DEFAULT
333
+ )
334
+
335
+ device_ct = ContentType.objects.get_for_model(Device)
336
+ name_rule_exists = RequiredValidationRule.objects.filter(content_type=device_ct, field="name").exists()
337
+
338
+ self.fields["DEVICE_NAME_REQUIRED"].initial = name_rule_exists
339
+
340
+ if user is not None and not user.is_staff:
341
+ for field in self.fields.values():
342
+ field.disabled = True
@@ -198,12 +198,6 @@ class Migration(migrations.Migration):
198
198
  default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
199
199
  ),
200
200
  ),
201
- ("created", models.DateTimeField(auto_now_add=True, null=True)),
202
- ("last_updated", models.DateTimeField(auto_now=True, null=True)),
203
- (
204
- "_custom_field_data",
205
- models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
206
- ),
207
201
  ("compliance_class_name", models.CharField(max_length=255)),
208
202
  ("last_validation_date", models.DateTimeField(auto_now=True)),
209
203
  ("object_id", models.UUIDField(blank=False, null=False)),
@@ -216,7 +210,6 @@ class Migration(migrations.Migration):
216
210
  "content_type",
217
211
  models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="contenttypes.contenttype"),
218
212
  ),
219
- ("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
220
213
  ],
221
214
  options={
222
215
  "verbose_name_plural": "Data Compliance",
@@ -136,9 +136,6 @@ def copy_app_data_to_core_data(apps, schema_editor):
136
136
  f"""\
137
137
  INSERT INTO data_validation_datacompliance (
138
138
  id,
139
- created,
140
- last_updated,
141
- _custom_field_data,
142
139
  compliance_class_name,
143
140
  last_validation_date,
144
141
  content_type_id,
@@ -150,9 +147,6 @@ INSERT INTO data_validation_datacompliance (
150
147
  message
151
148
  ) SELECT
152
149
  id,
153
- created,
154
- last_updated,
155
- _custom_field_data,
156
150
  compliance_class_name,
157
151
  last_validation_date,
158
152
  content_type_id,
@@ -274,9 +268,6 @@ def copy_core_data_to_app_data(apps, schema_editor):
274
268
  f"""\
275
269
  INSERT INTO nautobot_data_validation_engine_datacompliance (
276
270
  id,
277
- created,
278
- last_updated,
279
- _custom_field_data,
280
271
  compliance_class_name,
281
272
  last_validation_date,
282
273
  content_type_id,
@@ -288,9 +279,6 @@ INSERT INTO nautobot_data_validation_engine_datacompliance (
288
279
  message
289
280
  ) SELECT
290
281
  id,
291
- created,
292
- last_updated,
293
- _custom_field_data,
294
282
  compliance_class_name,
295
283
  last_validation_date,
296
284
  content_type_id,
@@ -9,10 +9,11 @@ from django.core.validators import MinValueValidator, ValidationError
9
9
  from django.db import models
10
10
 
11
11
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
12
- from nautobot.core.models import BaseManager
12
+ from nautobot.core.models import BaseManager, BaseModel
13
13
  from nautobot.core.models.generics import PrimaryModel
14
14
  from nautobot.core.models.querysets import RestrictedQuerySet
15
15
  from nautobot.core.utils.cache import construct_cache_key
16
+ from nautobot.extras.models.mixins import DynamicGroupsModelMixin, NotesMixin, SavedViewMixin
16
17
  from nautobot.extras.utils import extras_features, FeatureQuery
17
18
 
18
19
 
@@ -62,7 +63,7 @@ class ValidationRuleManager(BaseManager.from_queryset(RestrictedQuerySet)):
62
63
  return construct_cache_key(self, method_name="get_enabled_for_model", branch_aware=True)
63
64
 
64
65
 
65
- class ValidationRule(PrimaryModel):
66
+ class ValidationRuleModelMixin(models.Model):
66
67
  """Base model for all validation engine rule models."""
67
68
 
68
69
  name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
@@ -83,6 +84,8 @@ class ValidationRule(PrimaryModel):
83
84
  objects = ValidationRuleManager()
84
85
  documentation_static_path = "docs/user-guide/platform-functionality/data-validation.html"
85
86
 
87
+ is_data_compliance_model = False
88
+
86
89
  class Meta:
87
90
  """Model metadata for all validation engine rule models."""
88
91
 
@@ -101,7 +104,7 @@ class ValidationRule(PrimaryModel):
101
104
  "relationships",
102
105
  "webhooks",
103
106
  )
104
- class RegularExpressionValidationRule(ValidationRule):
107
+ class RegularExpressionValidationRule(ValidationRuleModelMixin, PrimaryModel):
105
108
  """A type of validation rule that applies a regular expression to a given model field."""
106
109
 
107
110
  regular_expression = models.TextField()
@@ -165,7 +168,7 @@ class RegularExpressionValidationRule(ValidationRule):
165
168
  "relationships",
166
169
  "webhooks",
167
170
  )
168
- class MinMaxValidationRule(ValidationRule):
171
+ class MinMaxValidationRule(ValidationRuleModelMixin, PrimaryModel):
169
172
  """A type of validation rule that applies min/max constraints to a given numeric model field."""
170
173
 
171
174
  min = models.FloatField(
@@ -231,7 +234,7 @@ class MinMaxValidationRule(ValidationRule):
231
234
  "relationships",
232
235
  "webhooks",
233
236
  )
234
- class RequiredValidationRule(ValidationRule):
237
+ class RequiredValidationRule(ValidationRuleModelMixin, PrimaryModel):
235
238
  """A type of validation rule that applies a required constraint to a given model field."""
236
239
 
237
240
  clone_fields = ["enabled", "content_type", "error_message"]
@@ -279,7 +282,7 @@ class RequiredValidationRule(ValidationRule):
279
282
  "relationships",
280
283
  "webhooks",
281
284
  )
282
- class UniqueValidationRule(ValidationRule):
285
+ class UniqueValidationRule(ValidationRuleModelMixin, PrimaryModel):
283
286
  """
284
287
  A type of validation rule that applies a unique constraint to a given model field.
285
288
 
@@ -321,7 +324,11 @@ class UniqueValidationRule(ValidationRule):
321
324
  raise ValidationError({"field": "This field is already unique by default."})
322
325
 
323
326
 
324
- class DataCompliance(PrimaryModel):
327
+ @extras_features(
328
+ "export_templates",
329
+ "graphql",
330
+ )
331
+ class DataCompliance(DynamicGroupsModelMixin, NotesMixin, SavedViewMixin, BaseModel):
325
332
  """Model to represent the results of an audit method."""
326
333
 
327
334
  compliance_class_name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=False, null=False)
@@ -335,6 +342,8 @@ class DataCompliance(PrimaryModel):
335
342
  valid = models.BooleanField(blank=False, null=False)
336
343
  message = models.TextField(blank=True, default="")
337
344
 
345
+ is_data_compliance_model = False
346
+
338
347
  class Meta:
339
348
  """Meta class for Audit model."""
340
349
 
@@ -1,11 +1,13 @@
1
1
  """App navigation menu items."""
2
2
 
3
3
  from nautobot.apps.ui import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab
4
+ from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
4
5
 
5
6
  menu_items = (
6
7
  NavMenuTab(
7
8
  name="Extensibility",
8
- icon="cast",
9
+ icon=NavigationIconChoices.EXTENSIBILITY,
10
+ weight=NavigationWeightChoices.EXTENSIBILITY,
9
11
  groups=(
10
12
  NavMenuGroup(
11
13
  name="Data Validation",
@@ -60,6 +62,11 @@ menu_items = (
60
62
  name="Data Compliance",
61
63
  permissions=["data_validation.view_datacompliance"],
62
64
  ),
65
+ NavMenuItem(
66
+ link="data_validation:device-constraints",
67
+ name="Device Constraints",
68
+ permissions=["dcim.view_device"],
69
+ ),
63
70
  ),
64
71
  ),
65
72
  ),
@@ -3,7 +3,7 @@
3
3
  from django.utils.html import format_html
4
4
  import django_tables2 as tables
5
5
 
6
- from nautobot.core.tables import BaseTable, TagColumn, ToggleColumn
6
+ from nautobot.core.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn
7
7
  from nautobot.data_validation.models import (
8
8
  DataCompliance,
9
9
  MinMaxValidationRule,
@@ -21,7 +21,9 @@ class RegularExpressionValidationRuleTable(BaseTable):
21
21
  """Base table for the RegularExpressionValidationRule model."""
22
22
 
23
23
  pk = ToggleColumn()
24
- name = tables.LinkColumn(order_by=("name",))
24
+ name = tables.Column(linkify=True, order_by=("name",))
25
+ enabled = BooleanColumn()
26
+ context_processing = BooleanColumn()
25
27
  tags = TagColumn()
26
28
 
27
29
  class Meta(BaseTable.Meta):
@@ -60,7 +62,8 @@ class MinMaxValidationRuleTable(BaseTable):
60
62
  """Base table for the MinMaxValidationRule model."""
61
63
 
62
64
  pk = ToggleColumn()
63
- name = tables.LinkColumn(order_by=("name",))
65
+ name = tables.Column(linkify=True, order_by=("name",))
66
+ enabled = BooleanColumn()
64
67
  tags = TagColumn()
65
68
 
66
69
  class Meta(BaseTable.Meta):
@@ -99,7 +102,8 @@ class RequiredValidationRuleTable(BaseTable):
99
102
  """Base table for the RequiredValidationRule model."""
100
103
 
101
104
  pk = ToggleColumn()
102
- name = tables.LinkColumn(order_by=("name",))
105
+ name = tables.Column(linkify=True, order_by=("name",))
106
+ enabled = BooleanColumn()
103
107
  tags = TagColumn()
104
108
 
105
109
  class Meta(BaseTable.Meta):
@@ -134,7 +138,8 @@ class UniqueValidationRuleTable(BaseTable):
134
138
  """Base table for the UniqueValidationRule model."""
135
139
 
136
140
  pk = ToggleColumn()
137
- name = tables.LinkColumn(order_by=("name",))
141
+ name = tables.Column(linkify=True, order_by=("name",))
142
+ enabled = BooleanColumn()
138
143
  tags = TagColumn()
139
144
 
140
145
  class Meta(BaseTable.Meta):
@@ -186,6 +191,7 @@ class DataComplianceTable(BaseTable):
186
191
  id = tables.Column(linkify=True, verbose_name="ID")
187
192
  validated_object = tables.RelatedLinkColumn()
188
193
  validated_attribute = ValidatedAttributeColumn()
194
+ valid = BooleanColumn()
189
195
 
190
196
  def order_validated_object(self, queryset, is_descending):
191
197
  """Reorder table by string representation of validated_object."""
@@ -226,6 +232,7 @@ class DataComplianceTableTab(BaseTable):
226
232
  """Base table for viewing the DataCompliance related to a single object."""
227
233
 
228
234
  validated_attribute = ValidatedAttributeColumn()
235
+ valid = BooleanColumn()
229
236
 
230
237
  class Meta(BaseTable.Meta):
231
238
  """Meta class for DataComplianceTableTab."""
@@ -1,3 +1,4 @@
1
+ {% comment %}Kept for use with apps that have not transitioned to the new UI Component framework.{% endcomment %}
1
2
  {% extends base_template %}
2
3
  {% load helpers %}
3
4
  {% load tz %}
@@ -0,0 +1,61 @@
1
+ {% extends "base.html" %}
2
+ {% load form_helpers %}
3
+ {% load ui_framework %}
4
+
5
+ {% block breadcrumbs_wrapper %}{% render_breadcrumbs %}{% endblock %}
6
+
7
+ {% block content %}
8
+ <form action="." method="post" class="h-100 vstack">
9
+ {% csrf_token %}
10
+ <div class="alert alert-warning d-flex align-items-center" role="alert">
11
+ <span aria-hidden="true" class="mdi mdi-alert me-2"></span>
12
+ <div>
13
+ <strong>Warning:</strong> Changes made here affect how device uniqueness and naming rules are enforced.
14
+ These changes will update the Constance configuration for <code>DEVICE_UNIQUENESS</code> and will create
15
+ a <code>RequiredValidationRule</code> for the <code>Device</code> model if
16
+ <i>Device name required</i> is selected.
17
+ Modify these settings <strong>at your own risk</strong>.
18
+ </div>
19
+ </div>
20
+ <div class="row align-content-start flex-fill">
21
+ <div class="col-md-8 offset-md-2">
22
+
23
+ {% if form.non_field_errors %}
24
+ <div class="card border-danger mb-16">
25
+ <div class="card-header bg-danger-subtle border-danger text-body">
26
+ <strong>Errors</strong>
27
+ </div>
28
+ <div class="card-body">
29
+ {{ form.non_field_errors }}
30
+ </div>
31
+ </div>
32
+ {% endif %}
33
+
34
+ <div class="card mb-16">
35
+ <div class="card-header"><strong>Device Constraints</strong></div>
36
+ <div class="card-body">
37
+ {% render_field form.DEVICE_UNIQUENESS %}
38
+ {% render_field form.DEVICE_NAME_REQUIRED %}
39
+ {% if not request.user.is_staff %}
40
+ <p class="text-secondary mt-2 text-center">
41
+ <em>You do not have permission to modify these settings.</em>
42
+ </p>
43
+ {% endif %}
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="nb-form-sticky-footer">
49
+ {% if request.user.is_staff %}
50
+ <button type="submit" name="_update" class="btn btn-primary">
51
+ <span aria-hidden="true" class="mdi mdi-check me-4"></span><!--
52
+ -->Update
53
+ </button>
54
+ <a href="{% url 'home' %}" class="btn btn-secondary">
55
+ <span aria-hidden="true" class="mdi mdi-close me-4"></span><!--
56
+ -->Cancel
57
+ </a>
58
+ {% endif %}
59
+ </div>
60
+ </form>
61
+ {% endblock %}
@@ -5,11 +5,11 @@ import contextlib
5
5
  from django.core.cache import cache
6
6
  import redis.exceptions
7
7
 
8
- from nautobot.data_validation.models import ValidationRule
8
+ from nautobot.data_validation.models import ValidationRuleModelMixin
9
9
 
10
10
 
11
11
  class ValidationRuleTestCaseMixin:
12
- model: type(ValidationRule)
12
+ model: type(ValidationRuleModelMixin)
13
13
 
14
14
  def tearDown(self):
15
15
  """Ensure that validation rule caches are cleared to avoid leakage into other tests."""
@@ -210,7 +210,26 @@ CREATE TABLE `nautobot_data_validation_engine_uniquevalidationrule` (
210
210
 
211
211
  def _populate_tables_postgresql(ContentType, DeviceType, VLAN):
212
212
  with connection.cursor() as cursor:
213
- # TODO: datacompliance
213
+ cursor.execute(
214
+ """\
215
+ INSERT INTO nautobot_data_validation_engine_datacompliance
216
+ VALUES (
217
+ 'f20e4572-84cf-4f28-9fe6-5f0f96f78d14',
218
+ '2025-10-21 16:07:55.123456+00',
219
+ '2025-10-21 16:07:55.123456+00',
220
+ '{}',
221
+ 'DcimDevicetypeCustomValidator',
222
+ '2025-10-21 16:07:55.123456+00',
223
+ '96591cd4-c4d1-4d69-982d-195bcea71a2c',
224
+ 'Juniper SRX300',
225
+ 'part_number',
226
+ '',
227
+ 'f',
228
+ 'A device type may only contain alpha numeric, dashes, and underscore characters.',
229
+ %s
230
+ );""",
231
+ [ContentType.objects.get_for_model(DeviceType).id],
232
+ )
214
233
 
215
234
  # Min/max validation rules
216
235
  cursor.execute(
@@ -269,7 +288,26 @@ VALUES (
269
288
 
270
289
  def _populate_tables_mysql(ContentType, DeviceType, VLAN):
271
290
  with connection.cursor() as cursor:
272
- # TODO: datacompliance
291
+ cursor.execute(
292
+ """\
293
+ INSERT INTO `nautobot_data_validation_engine_datacompliance`
294
+ VALUES (
295
+ 'f20e457284cf4f289fe65f0f96f78d14',
296
+ '2025-10-21 16:07:55.123456',
297
+ '2025-10-21 16:07:55.123456',
298
+ '{}',
299
+ 'DcimDevicetypeCustomValidator',
300
+ '2025-10-21 16:07:55.123456',
301
+ '96591cd4c4d14d69982d195bcea71a2c',
302
+ 'Juniper SRX300',
303
+ 'part_number',
304
+ '',
305
+ 0,
306
+ 'A device type may only contain alpha numeric, dashes, and underscore characters.',
307
+ %s
308
+ );""",
309
+ [ContentType.objects.get_for_model(DeviceType).id],
310
+ )
273
311
 
274
312
  # Min/max validation rules
275
313
  cursor.execute(
@@ -366,7 +404,7 @@ class DVEToDataValidationMigrationTestCase(MigratorTestCase):
366
404
  UniqueValidationRule = self.new_state.apps.get_model("data_validation", "uniquevalidationrule")
367
405
 
368
406
  with self.subTest("DataCompliance"):
369
- self.assertEqual(DataCompliance.objects.count(), 0)
407
+ self.assertEqual(DataCompliance.objects.count(), 1)
370
408
 
371
409
  with self.subTest("MinMaxValidationRule"):
372
410
  self.assertEqual(MinMaxValidationRule.objects.count(), 1)
@@ -397,6 +435,48 @@ class DataValidationToDVEMigrationTestCase(MigratorTestCase):
397
435
  else:
398
436
  raise ValueError(f"Unknown/unsupported database vendor {connection.vendor}")
399
437
 
438
+ ContentType = self.old_state.apps.get_model("contenttypes", "contenttype")
439
+ DeviceType = self.old_state.apps.get_model("dcim", "devicetype")
440
+ VLAN = self.old_state.apps.get_model("ipam", "vlan")
441
+
442
+ DataCompliance = self.old_state.apps.get_model("data_validation", "datacompliance")
443
+ MinMaxValidationRule = self.old_state.apps.get_model("data_validation", "minmaxvalidationrule")
444
+ RegularExpressionValidationRule = self.old_state.apps.get_model(
445
+ "data_validation", "regularexpressionvalidationrule"
446
+ )
447
+
448
+ DataCompliance.objects.create(
449
+ compliance_class_name="DcimDevicetypeCustomValidator",
450
+ object_id="96591cd4-c4d1-4d69-982d-195bcea71a2c",
451
+ validated_object_str="Juniper SRX300",
452
+ validated_attribute="part_number",
453
+ validated_attribute_value="",
454
+ valid=False,
455
+ message="A device type may only contain alpha numeric, dashes, and underscore characters.",
456
+ content_type=ContentType.objects.get_for_model(DeviceType),
457
+ )
458
+ MinMaxValidationRule.objects.create(
459
+ name="Max VLAN ID",
460
+ enabled=False,
461
+ error_message="",
462
+ field="vid",
463
+ min=None,
464
+ max=3999,
465
+ content_type=ContentType.objects.get_for_model(VLAN),
466
+ )
467
+ RegularExpressionValidationRule.objects.create(
468
+ name="Device Type Part Number",
469
+ enabled=True,
470
+ error_message="A device type may only contain alpha numeric, dashes, and underscore characters.",
471
+ field="part_number",
472
+ regular_expression="^[a-zA-Z0-9_-]+$",
473
+ content_type=ContentType.objects.get_for_model(DeviceType),
474
+ context_processing=False,
475
+ )
476
+
477
+ # TODO: requiredvalidationrule
478
+ # TODO: uniquevalidationrule
479
+
400
480
  def tearDown(self):
401
481
  super().tearDown()
402
482
  if connection.vendor == "postgresql":
@@ -19,10 +19,9 @@ class TestFailedDataComplianceRule(DataComplianceRule):
19
19
  # attribute
20
20
  raise ComplianceError(
21
21
  {
22
- "tenant": "Tenant",
23
- "description": "Description",
24
- "name": "Name",
25
- "status": "Status",
22
+ "tenant": "The tenant is wrong",
23
+ "name": "The name is wrong",
24
+ "status": "The status is wrong",
26
25
  }
27
26
  )
28
27
 
@@ -36,6 +35,12 @@ class TestPassedDataComplianceRule(DataComplianceRule):
36
35
  """No exception means the audit passes."""
37
36
 
38
37
 
38
+ class TestFailedDataComplianceRuleAlt(TestFailedDataComplianceRule):
39
+ """Test implementation of DataComplianceRule, for dcim.rack."""
40
+
41
+ model = "dcim.device"
42
+
43
+
39
44
  class TestCompliance(TestCase):
40
45
  """Test DataComplianceRule methods."""
41
46
 
@@ -62,7 +67,7 @@ class TestCompliance(TestCase):
62
67
 
63
68
  def test_audit_fail(self):
64
69
  result = DataCompliance.objects.filter(valid=False).all()
65
- self.assertEqual(len(result), 5)
70
+ self.assertEqual(len(result), 4)
66
71
  result = DataCompliance.objects.get(validated_attribute="tenant")
67
72
  self.assertEqual(result.compliance_class_name, "TestFailedDataComplianceRule")
68
73
  self.assertEqual(result.validated_object, self.s)
@@ -71,10 +76,10 @@ class TestCompliance(TestCase):
71
76
 
72
77
  def test_validate_replaces_results(self):
73
78
  self.assertEqual(
74
- len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)), 5
79
+ len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)), 4
75
80
  )
76
81
  TestFailedDataComplianceRule(self.s).clean()
77
82
  self.assertEqual(
78
83
  len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)),
79
- 5,
84
+ 4,
80
85
  )
@@ -40,9 +40,7 @@ class ValidationRuleFilterTestCaseMixin(ValidationRuleTestCaseMixin):
40
40
  self.assertQuerysetEqualAndNotEmpty(self.filterset(params, self.queryset).qs, expected_queryset)
41
41
 
42
42
 
43
- class RegularExpressionValidationRuleFilterTestCase(
44
- ValidationRuleFilterTestCaseMixin, FilterTestCases.NameOnlyFilterTestCase
45
- ):
43
+ class RegularExpressionValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
46
44
  """
47
45
  Filterset test cases for the RegularExpressionValidationRule model
48
46
  """
@@ -52,6 +50,7 @@ class RegularExpressionValidationRuleFilterTestCase(
52
50
  filterset = RegularExpressionValidationRuleFilterSet
53
51
  generic_filter_tests = [
54
52
  ("id",),
53
+ ("name",),
55
54
  ("regular_expression",),
56
55
  ("error_message",),
57
56
  ("field",),
@@ -91,7 +90,7 @@ class RegularExpressionValidationRuleFilterTestCase(
91
90
  )
92
91
 
93
92
 
94
- class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.NameOnlyFilterTestCase):
93
+ class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
95
94
  """
96
95
  Filterset test cases for the MinMaxValidationRule model
97
96
  """
@@ -101,6 +100,7 @@ class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
101
100
  filterset = MinMaxValidationRuleFilterSet
102
101
  generic_filter_tests = [
103
102
  ("id",),
103
+ ("name",),
104
104
  ("error_message",),
105
105
  ("field",),
106
106
  ]
@@ -144,7 +144,7 @@ class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
144
144
  self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
145
145
 
146
146
 
147
- class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.NameOnlyFilterTestCase):
147
+ class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
148
148
  """
149
149
  Filterset test cases for the RequiredValidationRule model
150
150
  """
@@ -154,6 +154,7 @@ class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Fi
154
154
  filterset = RequiredValidationRuleFilterSet
155
155
  generic_filter_tests = [
156
156
  ("id",),
157
+ ("name",),
157
158
  ("error_message",),
158
159
  ("field",),
159
160
  ]
@@ -189,7 +190,7 @@ class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Fi
189
190
  )
190
191
 
191
192
 
192
- class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.NameOnlyFilterTestCase):
193
+ class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
193
194
  """
194
195
  Filterset test cases for the UniqueValidationRule model
195
196
  """
@@ -199,6 +200,7 @@ class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
199
200
  filterset = UniqueValidationRuleFilterSet
200
201
  generic_filter_tests = [
201
202
  ("id",),
203
+ ("name",),
202
204
  ("error_message",),
203
205
  ("field",),
204
206
  ("max_instances",),