nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__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.
Files changed (659) hide show
  1. nautobot/apps/choices.py +4 -2
  2. nautobot/apps/filters.py +7 -9
  3. nautobot/apps/models.py +2 -2
  4. nautobot/apps/ui.py +13 -1
  5. nautobot/apps/utils.py +8 -0
  6. nautobot/circuits/filters.py +3 -2
  7. nautobot/circuits/navigation.py +3 -2
  8. nautobot/circuits/templates/circuits/circuit_create.html +3 -3
  9. nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
  10. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
  11. nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
  12. nautobot/circuits/tests/integration/test_circuit.py +10 -13
  13. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
  14. nautobot/circuits/views.py +6 -2
  15. nautobot/cloud/filters.py +1 -1
  16. nautobot/cloud/navigation.py +3 -2
  17. nautobot/core/api/schema.py +1 -1
  18. nautobot/core/api/serializers.py +6 -1
  19. nautobot/core/api/urls.py +2 -0
  20. nautobot/core/api/views.py +12 -0
  21. nautobot/core/apps/__init__.py +11 -10
  22. nautobot/core/celery/__init__.py +3 -5
  23. nautobot/core/checks.py +46 -0
  24. nautobot/core/choices.py +1 -1
  25. nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
  26. nautobot/core/cli/migrate_deprecated_templates.py +227 -0
  27. nautobot/core/constants.py +3 -0
  28. nautobot/core/context_processors.py +9 -1
  29. nautobot/core/filters.py +4 -0
  30. nautobot/core/forms/__init__.py +2 -0
  31. nautobot/core/forms/forms.py +1 -1
  32. nautobot/core/forms/widgets.py +21 -2
  33. nautobot/core/jobs/__init__.py +62 -3
  34. nautobot/core/jobs/groups.py +31 -1
  35. nautobot/core/management/commands/generate_test_data.py +28 -9
  36. nautobot/core/models/__init__.py +11 -0
  37. nautobot/core/models/generics.py +9 -1
  38. nautobot/core/models/tree_queries.py +10 -5
  39. nautobot/core/models/utils.py +1 -1
  40. nautobot/core/settings.py +35 -19
  41. nautobot/core/settings.yaml +17 -33
  42. nautobot/core/signals.py +12 -1
  43. nautobot/core/tables.py +13 -6
  44. nautobot/core/templates/40x.html +1 -1
  45. nautobot/core/templates/500.html +2 -2
  46. nautobot/core/templates/admin/base.html +1 -2
  47. nautobot/core/templates/admin/change_list.html +9 -12
  48. nautobot/core/templates/admin/config/config.html +12 -12
  49. nautobot/core/templates/admin/index.html +3 -3
  50. nautobot/core/templates/base_django.html +1 -2
  51. nautobot/core/templates/buttons/export.html +1 -1
  52. nautobot/core/templates/components/button/dropdown.html +5 -3
  53. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  54. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  55. nautobot/core/templates/components/panel/panel.html +3 -3
  56. nautobot/core/templates/components/tab/content_wrapper.html +6 -7
  57. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
  58. nautobot/core/templates/echarts/echarts.html +22 -9
  59. nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
  60. nautobot/core/templates/generic/object_bulk_create.html +6 -5
  61. nautobot/core/templates/generic/object_bulk_delete.html +1 -1
  62. nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
  63. nautobot/core/templates/generic/object_bulk_edit.html +1 -1
  64. nautobot/core/templates/generic/object_bulk_import.html +1 -1
  65. nautobot/core/templates/generic/object_bulk_remove.html +2 -2
  66. nautobot/core/templates/generic/object_bulk_update.html +5 -4
  67. nautobot/core/templates/generic/object_create.html +5 -4
  68. nautobot/core/templates/generic/object_delete.html +1 -1
  69. nautobot/core/templates/generic/object_detail.html +1 -1
  70. nautobot/core/templates/generic/object_edit.html +1 -1
  71. nautobot/core/templates/generic/object_import.html +2 -1
  72. nautobot/core/templates/generic/object_list.html +12 -4
  73. nautobot/core/templates/generic/object_notes.html +5 -3
  74. nautobot/core/templates/generic/object_retrieve.html +4 -5
  75. nautobot/core/templates/graphene/graphiql.html +7 -8
  76. nautobot/core/templates/home.html +1 -1
  77. nautobot/core/templates/import_success.html +2 -1
  78. nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
  79. nautobot/core/templates/inc/created_updated.html +7 -3
  80. nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
  81. nautobot/core/templates/inc/footer.html +3 -1
  82. nautobot/core/templates/inc/form_static_field.html +6 -0
  83. nautobot/core/templates/inc/header.html +11 -1
  84. nautobot/core/templates/inc/image_attachments.html +2 -1
  85. nautobot/core/templates/inc/media.html +14 -0
  86. nautobot/core/templates/inc/nav_menu.html +3 -9
  87. nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
  88. nautobot/core/templates/inc/search_panel.html +4 -4
  89. nautobot/core/templates/login.html +4 -2
  90. nautobot/core/templates/nautobot_config.py.j2 +6 -11
  91. nautobot/core/templates/redoc_ui.html +7 -0
  92. nautobot/core/templates/rest_framework/api.html +103 -2
  93. nautobot/core/templates/search.html +1 -1
  94. nautobot/core/templates/swagger_ui.html +17 -3
  95. nautobot/core/templates/system_jobs/import_objects.html +1 -2
  96. nautobot/core/templates/utilities/confirmation_form.html +2 -2
  97. nautobot/core/templates/utilities/obj_table.html +10 -2
  98. nautobot/core/templates/utilities/render_field.html +7 -7
  99. nautobot/core/templates/utilities/render_jinja2.html +2 -2
  100. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
  101. nautobot/core/templates/utilities/theme_preview.html +19 -3
  102. nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
  103. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  104. nautobot/core/templatetags/helpers.py +76 -18
  105. nautobot/core/testing/api.py +68 -9
  106. nautobot/core/testing/filters.py +0 -23
  107. nautobot/core/testing/integration.py +41 -17
  108. nautobot/core/testing/mixins.py +2 -0
  109. nautobot/core/testing/utils.py +18 -4
  110. nautobot/core/testing/views.py +104 -13
  111. nautobot/core/tests/integration/test_app_home.py +34 -30
  112. nautobot/core/tests/integration/test_app_navbar.py +3 -0
  113. nautobot/core/tests/integration/test_filters.py +48 -11
  114. nautobot/core/tests/integration/test_theme.py +22 -21
  115. nautobot/core/tests/nautobot_config.py +3 -0
  116. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  117. nautobot/core/tests/runner.py +8 -1
  118. nautobot/core/tests/test_api.py +5 -3
  119. nautobot/core/tests/test_breadcrumbs.py +27 -28
  120. nautobot/core/tests/test_checks.py +28 -0
  121. nautobot/core/tests/test_cli.py +40 -0
  122. nautobot/core/tests/test_config.py +2 -1
  123. nautobot/core/tests/test_forms.py +55 -13
  124. nautobot/core/tests/test_jobs.py +144 -3
  125. nautobot/core/tests/test_nautobot_server.py +2 -0
  126. nautobot/core/tests/test_navigations.py +76 -1
  127. nautobot/core/tests/test_patch_social_django.py +42 -0
  128. nautobot/core/tests/test_renderers.py +59 -0
  129. nautobot/core/tests/test_settings_schema.py +1 -0
  130. nautobot/core/tests/test_tables.py +3 -1
  131. nautobot/core/tests/test_templatetags_helpers.py +62 -13
  132. nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
  133. nautobot/core/tests/test_titles.py +0 -16
  134. nautobot/core/tests/test_tree_queries.py +14 -1
  135. nautobot/core/tests/test_ui.py +123 -4
  136. nautobot/core/tests/test_utils.py +72 -5
  137. nautobot/core/tests/test_views.py +159 -31
  138. nautobot/core/ui/breadcrumbs.py +70 -29
  139. nautobot/core/ui/bulk_buttons.py +1 -1
  140. nautobot/core/ui/choices.py +143 -27
  141. nautobot/core/ui/constants.py +76 -12
  142. nautobot/core/ui/echarts.py +15 -20
  143. nautobot/core/ui/object_detail.py +143 -55
  144. nautobot/core/ui/titles.py +3 -6
  145. nautobot/core/urls.py +20 -9
  146. nautobot/core/utils/cache.py +2 -1
  147. nautobot/core/utils/filtering.py +28 -18
  148. nautobot/core/utils/lookup.py +49 -8
  149. nautobot/core/utils/module_loading.py +21 -0
  150. nautobot/core/utils/patch_social_django.py +128 -0
  151. nautobot/core/views/__init__.py +38 -1
  152. nautobot/core/views/generic.py +3 -3
  153. nautobot/core/views/mixins.py +45 -22
  154. nautobot/core/views/renderers.py +4 -3
  155. nautobot/core/views/viewsets.py +2 -1
  156. nautobot/data_validation/apps.py +1 -5
  157. nautobot/data_validation/custom_validators.py +4 -4
  158. nautobot/data_validation/filters.py +1 -1
  159. nautobot/data_validation/forms.py +40 -0
  160. nautobot/data_validation/migrations/0001_initial.py +0 -7
  161. nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
  162. nautobot/data_validation/models.py +16 -7
  163. nautobot/data_validation/navigation.py +8 -1
  164. nautobot/data_validation/tables.py +12 -5
  165. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
  166. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  167. nautobot/data_validation/tests/__init__.py +2 -2
  168. nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
  169. nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
  170. nautobot/data_validation/tests/test_filters.py +8 -6
  171. nautobot/data_validation/tests/test_models.py +15 -0
  172. nautobot/data_validation/tests/test_views.py +190 -32
  173. nautobot/data_validation/urls.py +2 -5
  174. nautobot/data_validation/views.py +73 -40
  175. nautobot/dcim/api/serializers.py +3 -13
  176. nautobot/dcim/apps.py +4 -0
  177. nautobot/dcim/choices.py +65 -0
  178. nautobot/dcim/constants.py +7 -0
  179. nautobot/dcim/custom_validators.py +84 -0
  180. nautobot/dcim/factory.py +1 -1
  181. nautobot/dcim/filter_mixins.py +353 -4
  182. nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
  183. nautobot/dcim/forms.py +90 -4
  184. nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
  185. nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
  186. nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
  187. nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
  188. nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
  189. nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
  190. nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
  191. nautobot/dcim/models/device_component_templates.py +33 -1
  192. nautobot/dcim/models/device_components.py +98 -64
  193. nautobot/dcim/models/devices.py +30 -20
  194. nautobot/dcim/navigation.py +7 -6
  195. nautobot/dcim/tables/devices.py +18 -0
  196. nautobot/dcim/tables/devicetypes.py +8 -1
  197. nautobot/dcim/tables/racks.py +0 -2
  198. nautobot/dcim/tables/template_code.py +15 -15
  199. nautobot/dcim/templates/dcim/cable_connect.html +28 -112
  200. nautobot/dcim/templates/dcim/cable_trace.html +0 -4
  201. nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
  202. nautobot/dcim/templates/dcim/consoleport.html +7 -6
  203. nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
  204. nautobot/dcim/templates/dcim/device/config.html +2 -2
  205. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  206. nautobot/dcim/templates/dcim/device/status.html +8 -8
  207. nautobot/dcim/templates/dcim/device.html +1 -1
  208. nautobot/dcim/templates/dcim/device_component_add.html +2 -2
  209. nautobot/dcim/templates/dcim/device_create.html +5 -3
  210. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  211. nautobot/dcim/templates/dcim/device_list.html +73 -10
  212. nautobot/dcim/templates/dcim/devicebay.html +1 -1
  213. nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
  214. nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
  215. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  216. nautobot/dcim/templates/dcim/frontport.html +10 -9
  217. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  218. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  219. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
  220. nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
  221. nautobot/dcim/templates/dcim/interface.html +35 -7
  222. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  223. nautobot/dcim/templates/dcim/interface_edit.html +2 -0
  224. nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
  225. nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
  226. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  227. nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
  228. nautobot/dcim/templates/dcim/module/base.html +49 -9
  229. nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
  230. nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
  231. nautobot/dcim/templates/dcim/module_frontports.html +1 -1
  232. nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
  233. nautobot/dcim/templates/dcim/module_list.html +57 -8
  234. nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
  235. nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
  236. nautobot/dcim/templates/dcim/module_powerports.html +1 -1
  237. nautobot/dcim/templates/dcim/module_rearports.html +1 -1
  238. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  239. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  240. nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
  241. nautobot/dcim/templates/dcim/platform_create.html +1 -1
  242. nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
  243. nautobot/dcim/templates/dcim/powerport.html +6 -5
  244. nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
  245. nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
  246. nautobot/dcim/templates/dcim/rearport.html +8 -7
  247. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  248. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
  249. nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
  250. nautobot/dcim/tests/integration/test_controller.py +4 -6
  251. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
  252. nautobot/dcim/tests/integration/test_create_device.py +0 -2
  253. nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
  254. nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
  255. nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
  256. nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
  257. nautobot/dcim/tests/test_api.py +194 -6
  258. nautobot/dcim/tests/test_custom_validators.py +229 -0
  259. nautobot/dcim/tests/test_filters.py +55 -7
  260. nautobot/dcim/tests/test_forms.py +110 -8
  261. nautobot/dcim/tests/test_graphql.py +44 -1
  262. nautobot/dcim/tests/test_models.py +328 -4
  263. nautobot/dcim/tests/test_tables.py +160 -0
  264. nautobot/dcim/tests/test_views.py +132 -29
  265. nautobot/dcim/urls.py +64 -21
  266. nautobot/dcim/utils.py +3 -3
  267. nautobot/dcim/views.py +777 -397
  268. nautobot/extras/api/views.py +60 -45
  269. nautobot/extras/choices.py +2 -13
  270. nautobot/extras/datasources/git.py +3 -1
  271. nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
  272. nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
  273. nautobot/extras/{filters/__init__.py → filters.py} +33 -48
  274. nautobot/extras/forms/forms.py +14 -15
  275. nautobot/extras/forms/mixins.py +0 -41
  276. nautobot/extras/jobs.py +2 -0
  277. nautobot/extras/jobs_ui.py +4 -3
  278. nautobot/extras/management/__init__.py +11 -0
  279. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
  280. nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
  281. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  282. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  283. nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
  284. nautobot/extras/models/__init__.py +1 -2
  285. nautobot/extras/models/approvals.py +33 -14
  286. nautobot/extras/models/change_logging.py +4 -0
  287. nautobot/extras/models/contacts.py +2 -0
  288. nautobot/extras/models/groups.py +44 -5
  289. nautobot/extras/models/jobs.py +60 -4
  290. nautobot/extras/models/mixins.py +28 -0
  291. nautobot/extras/models/models.py +23 -2
  292. nautobot/extras/models/secrets.py +1 -0
  293. nautobot/extras/models/statuses.py +0 -15
  294. nautobot/extras/navigation.py +13 -9
  295. nautobot/extras/plugins/__init__.py +33 -55
  296. nautobot/extras/plugins/marketplace_manifest.yml +49 -1
  297. nautobot/extras/plugins/tables.py +3 -3
  298. nautobot/extras/plugins/urls.py +2 -21
  299. nautobot/extras/plugins/utils.py +1 -33
  300. nautobot/extras/plugins/views.py +0 -9
  301. nautobot/extras/querysets.py +8 -0
  302. nautobot/extras/signals.py +20 -19
  303. nautobot/extras/tables.py +64 -68
  304. nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
  305. nautobot/extras/templates/extras/approval_dashboard.html +7 -5
  306. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
  307. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
  308. nautobot/extras/templates/extras/configcontext_update.html +1 -0
  309. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  310. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  311. nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
  312. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  313. nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
  314. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
  315. nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
  316. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  317. nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
  318. nautobot/extras/templates/extras/inc/json_format.html +10 -3
  319. nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
  320. nautobot/extras/templates/extras/job.html +29 -12
  321. nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
  322. nautobot/extras/templates/extras/job_edit.html +52 -46
  323. nautobot/extras/templates/extras/job_list.html +29 -25
  324. nautobot/extras/templates/extras/marketplace.html +5 -9
  325. nautobot/extras/templates/extras/object_configcontext.html +1 -1
  326. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  327. nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
  328. nautobot/extras/templates/extras/plugin_detail.html +29 -24
  329. nautobot/extras/templates/extras/plugins_list.html +16 -26
  330. nautobot/extras/templates/extras/role_retrieve.html +64 -0
  331. nautobot/extras/templates/extras/scheduledjob.html +4 -2
  332. nautobot/extras/templates/extras/secret_create.html +1 -1
  333. nautobot/extras/templatetags/custom_links.py +12 -12
  334. nautobot/extras/templatetags/job_buttons.py +14 -12
  335. nautobot/extras/test_jobs/invalid_import.py +9 -0
  336. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  337. nautobot/extras/test_jobs/missing_import.py +11 -0
  338. nautobot/extras/tests/integration/test_computedfields.py +8 -9
  339. nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
  340. nautobot/extras/tests/integration/test_customfields.py +9 -10
  341. nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
  342. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  343. nautobot/extras/tests/integration/test_plugins.py +18 -6
  344. nautobot/extras/tests/integration/test_relationships.py +0 -2
  345. nautobot/extras/tests/test_api.py +90 -18
  346. nautobot/extras/tests/test_approvals.py +38 -38
  347. nautobot/extras/tests/test_changelog.py +59 -5
  348. nautobot/extras/tests/test_customfields.py +22 -13
  349. nautobot/extras/tests/test_customfields_filters.py +479 -0
  350. nautobot/extras/tests/test_dynamicgroups.py +39 -1
  351. nautobot/extras/tests/test_filters.py +57 -22
  352. nautobot/extras/tests/test_forms.py +18 -21
  353. nautobot/extras/tests/test_jobs.py +25 -4
  354. nautobot/extras/tests/test_migrations.py +1 -0
  355. nautobot/extras/tests/test_models.py +51 -33
  356. nautobot/extras/tests/test_plugins.py +36 -10
  357. nautobot/extras/tests/test_utils.py +3 -4
  358. nautobot/extras/tests/test_views.py +52 -112
  359. nautobot/extras/urls.py +0 -14
  360. nautobot/extras/views.py +164 -71
  361. nautobot/ipam/factory.py +7 -0
  362. nautobot/ipam/filter_mixins.py +38 -0
  363. nautobot/ipam/filters.py +53 -38
  364. nautobot/ipam/formfields.py +1 -1
  365. nautobot/ipam/forms.py +6 -3
  366. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  367. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  368. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  369. nautobot/ipam/models.py +29 -2
  370. nautobot/ipam/navigation.py +3 -2
  371. nautobot/ipam/signals.py +71 -0
  372. nautobot/ipam/tables.py +19 -6
  373. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  374. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
  375. nautobot/ipam/templates/ipam/ipaddress.html +14 -0
  376. nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
  377. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
  378. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  379. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  380. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  381. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  382. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  383. nautobot/ipam/templates/ipam/prefix_list.html +14 -13
  384. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  385. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  386. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  387. nautobot/ipam/tests/test_api.py +13 -6
  388. nautobot/ipam/tests/test_filters.py +36 -1
  389. nautobot/ipam/tests/test_forms.py +1 -1
  390. nautobot/ipam/tests/test_models.py +44 -2
  391. nautobot/ipam/tests/test_tables.py +1 -2
  392. nautobot/ipam/tests/test_utils.py +1 -1
  393. nautobot/ipam/tests/test_views.py +13 -14
  394. nautobot/ipam/ui.py +0 -17
  395. nautobot/ipam/utils/migrations.py +16 -2
  396. nautobot/ipam/utils/testing.py +9 -3
  397. nautobot/ipam/views.py +53 -11
  398. nautobot/load_balancers/__init__.py +0 -0
  399. nautobot/load_balancers/api/__init__.py +1 -0
  400. nautobot/load_balancers/api/serializers.py +75 -0
  401. nautobot/load_balancers/api/urls.py +23 -0
  402. nautobot/load_balancers/api/views.py +61 -0
  403. nautobot/load_balancers/apps.py +17 -0
  404. nautobot/load_balancers/choices.py +167 -0
  405. nautobot/load_balancers/filters.py +225 -0
  406. nautobot/load_balancers/forms.py +532 -0
  407. nautobot/load_balancers/management/commands/__init__.py +0 -0
  408. nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
  409. nautobot/load_balancers/migrations/0001_initial.py +465 -0
  410. nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
  411. nautobot/load_balancers/migrations/__init__.py +0 -0
  412. nautobot/load_balancers/models.py +423 -0
  413. nautobot/load_balancers/navigation.py +80 -0
  414. nautobot/load_balancers/tables.py +255 -0
  415. nautobot/load_balancers/tests/__init__.py +474 -0
  416. nautobot/load_balancers/tests/test_api.py +353 -0
  417. nautobot/load_balancers/tests/test_filters.py +134 -0
  418. nautobot/load_balancers/tests/test_forms.py +266 -0
  419. nautobot/load_balancers/tests/test_models.py +195 -0
  420. nautobot/load_balancers/tests/test_views.py +229 -0
  421. nautobot/load_balancers/urls.py +17 -0
  422. nautobot/load_balancers/views.py +248 -0
  423. nautobot/project-static/dist/css/github-dark.min.css +10 -0
  424. nautobot/project-static/dist/css/github.min.css +10 -0
  425. nautobot/project-static/dist/css/nautobot.css +1 -11
  426. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  427. nautobot/project-static/dist/js/libraries.js +1 -1
  428. nautobot/project-static/dist/js/libraries.js.map +1 -1
  429. nautobot/project-static/dist/js/nautobot.js +1 -1
  430. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  431. nautobot/project-static/js/cabletrace.js +1 -1
  432. nautobot/project-static/js/forms.js +13 -0
  433. nautobot/project-static/js/interface_filtering.js +20 -16
  434. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  435. nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
  436. nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
  437. nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
  438. nautobot/project-static/nautobot-icons/cloud.svg +1 -1
  439. nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
  440. nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
  441. nautobot/project-static/nautobot-icons/elements.svg +1 -1
  442. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  443. nautobot/project-static/nautobot-icons/hammer.svg +1 -1
  444. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  445. nautobot/project-static/nautobot-icons/secrets.svg +1 -1
  446. nautobot/project-static/nautobot-icons/security.svg +3 -0
  447. nautobot/project-static/nautobot-icons/server.svg +1 -1
  448. nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
  449. nautobot/project-static/nautobot-icons/star.svg +1 -1
  450. nautobot/tenancy/api/serializers.py +1 -0
  451. nautobot/tenancy/api/views.py +2 -1
  452. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  453. nautobot/tenancy/navigation.py +3 -1
  454. nautobot/tenancy/tests/test_filters.py +0 -2
  455. nautobot/tenancy/views.py +2 -1
  456. nautobot/ui/package-lock.json +87 -4
  457. nautobot/ui/package.json +2 -1
  458. nautobot/ui/src/js/collapse.js +3 -3
  459. nautobot/ui/src/js/nautobot.js +16 -1
  460. nautobot/ui/src/js/select2.js +53 -2
  461. nautobot/ui/src/scss/colors.scss +1 -1
  462. nautobot/ui/src/scss/nautobot.scss +112 -30
  463. nautobot/ui/webpack.config.js +13 -0
  464. nautobot/users/templates/users/preferences.html +11 -2
  465. nautobot/users/templates/users/profile.html +45 -12
  466. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  467. nautobot/users/tests/test_api.py +4 -0
  468. nautobot/users/views.py +4 -2
  469. nautobot/virtualization/filters.py +6 -1
  470. nautobot/virtualization/models.py +1 -68
  471. nautobot/virtualization/navigation.py +3 -2
  472. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  473. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
  474. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
  475. nautobot/virtualization/tests/test_api.py +3 -0
  476. nautobot/virtualization/tests/test_filters.py +10 -1
  477. nautobot/virtualization/tests/test_models.py +45 -4
  478. nautobot/virtualization/views.py +4 -1
  479. nautobot/vpn/__init__.py +0 -0
  480. nautobot/vpn/api/serializers.py +113 -0
  481. nautobot/vpn/api/urls.py +19 -0
  482. nautobot/vpn/api/views.py +70 -0
  483. nautobot/vpn/apps.py +8 -0
  484. nautobot/vpn/choices.py +171 -0
  485. nautobot/vpn/factory.py +219 -0
  486. nautobot/vpn/filters.py +234 -0
  487. nautobot/vpn/forms.py +487 -0
  488. nautobot/vpn/homepage.py +19 -0
  489. nautobot/vpn/migrations/0001_initial.py +541 -0
  490. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  491. nautobot/vpn/migrations/__init__.py +0 -0
  492. nautobot/vpn/models.py +535 -0
  493. nautobot/vpn/navigation.py +98 -0
  494. nautobot/vpn/tables.py +383 -0
  495. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  496. nautobot/vpn/tests/__init__.py +0 -0
  497. nautobot/vpn/tests/test_api.py +336 -0
  498. nautobot/vpn/tests/test_filters.py +139 -0
  499. nautobot/vpn/tests/test_forms.py +293 -0
  500. nautobot/vpn/tests/test_models.py +147 -0
  501. nautobot/vpn/tests/test_views.py +300 -0
  502. nautobot/vpn/urls.py +16 -0
  503. nautobot/vpn/views.py +495 -0
  504. nautobot/wireless/navigation.py +3 -2
  505. nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
  506. nautobot/wireless/tests/test_api.py +1 -1
  507. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
  508. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
  509. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
  510. nautobot/circuits/templates/circuits/circuit.html +0 -2
  511. nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
  512. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
  513. nautobot/circuits/templates/circuits/circuit_update.html +0 -1
  514. nautobot/circuits/templates/circuits/circuittermination.html +0 -2
  515. nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
  516. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
  517. nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
  518. nautobot/circuits/templates/circuits/circuittype.html +0 -2
  519. nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
  520. nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
  521. nautobot/circuits/templates/circuits/provider.html +0 -2
  522. nautobot/circuits/templates/circuits/provider_edit.html +0 -2
  523. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
  524. nautobot/circuits/templates/circuits/provider_update.html +0 -1
  525. nautobot/circuits/templates/circuits/providernetwork.html +0 -2
  526. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  527. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
  528. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
  529. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
  530. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
  531. nautobot/core/templates/buttons/import.html +0 -9
  532. nautobot/data_validation/template_content.py +0 -42
  533. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
  534. nautobot/dcim/filters/mixins.py +0 -354
  535. nautobot/dcim/templates/dcim/controller/base.html +0 -2
  536. nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
  537. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
  538. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
  539. nautobot/dcim/templates/dcim/device/base.html +0 -2
  540. nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
  541. nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
  542. nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
  543. nautobot/dcim/templates/dcim/device/frontports.html +0 -2
  544. nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
  545. nautobot/dcim/templates/dcim/device/inventory.html +0 -2
  546. nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
  547. nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
  548. nautobot/dcim/templates/dcim/device/powerports.html +0 -2
  549. nautobot/dcim/templates/dcim/device/rearports.html +0 -2
  550. nautobot/dcim/templates/dcim/device/wireless.html +0 -2
  551. nautobot/dcim/templates/dcim/device_component.html +0 -2
  552. nautobot/dcim/templates/dcim/device_edit.html +0 -2
  553. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
  554. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
  555. nautobot/dcim/templates/dcim/devicetype.html +0 -2
  556. nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
  557. nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
  558. nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
  559. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
  560. nautobot/dcim/templates/dcim/location.html +0 -2
  561. nautobot/dcim/templates/dcim/location_edit.html +0 -2
  562. nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
  563. nautobot/dcim/templates/dcim/locationtype.html +0 -2
  564. nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
  565. nautobot/dcim/templates/dcim/manufacturer.html +0 -2
  566. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
  567. nautobot/dcim/templates/dcim/platform.html +0 -2
  568. nautobot/dcim/templates/dcim/powerfeed.html +0 -2
  569. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
  570. nautobot/dcim/templates/dcim/powerpanel.html +0 -2
  571. nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
  572. nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
  573. nautobot/dcim/templates/dcim/rack.html +0 -2
  574. nautobot/dcim/templates/dcim/rack_edit.html +0 -2
  575. nautobot/dcim/templates/dcim/rackgroup.html +0 -2
  576. nautobot/dcim/templates/dcim/rackreservation.html +0 -2
  577. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
  578. nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
  579. nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
  580. nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
  581. nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
  582. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
  583. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
  584. nautobot/dcim/ui.py +0 -29
  585. nautobot/extras/templates/extras/computedfield.html +0 -2
  586. nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
  587. nautobot/extras/templates/extras/configcontext.html +0 -2
  588. nautobot/extras/templates/extras/configcontext_edit.html +0 -2
  589. nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
  590. nautobot/extras/templates/extras/configcontextschema.html +0 -2
  591. nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
  592. nautobot/extras/templates/extras/contact_retrieve.html +0 -2
  593. nautobot/extras/templates/extras/customfield.html +0 -2
  594. nautobot/extras/templates/extras/customfield_edit.html +0 -2
  595. nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
  596. nautobot/extras/templates/extras/customlink.html +0 -2
  597. nautobot/extras/templates/extras/dynamicgroup.html +0 -2
  598. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
  599. nautobot/extras/templates/extras/exporttemplate.html +0 -2
  600. nautobot/extras/templates/extras/gitrepository.html +0 -2
  601. nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
  602. nautobot/extras/templates/extras/graphqlquery.html +0 -2
  603. nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
  604. nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
  605. nautobot/extras/templates/extras/job_detail.html +0 -2
  606. nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
  607. nautobot/extras/templates/extras/jobhook.html +0 -2
  608. nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
  609. nautobot/extras/templates/extras/jobresult.html +0 -2
  610. nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
  611. nautobot/extras/templates/extras/note.html +0 -2
  612. nautobot/extras/templates/extras/note_retrieve.html +0 -1
  613. nautobot/extras/templates/extras/object_changelog.html +0 -2
  614. nautobot/extras/templates/extras/object_notes.html +0 -2
  615. nautobot/extras/templates/extras/objectchange.html +0 -2
  616. nautobot/extras/templates/extras/objectchange_list.html +0 -3
  617. nautobot/extras/templates/extras/relationship.html +0 -1
  618. nautobot/extras/templates/extras/secret.html +0 -1
  619. nautobot/extras/templates/extras/secret_edit.html +0 -1
  620. nautobot/extras/templates/extras/secretsgroup.html +0 -2
  621. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
  622. nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
  623. nautobot/extras/templates/extras/status.html +0 -2
  624. nautobot/extras/templates/extras/tag.html +0 -2
  625. nautobot/extras/templates/extras/tag_edit.html +0 -2
  626. nautobot/extras/templates/extras/tag_retrieve.html +0 -2
  627. nautobot/extras/templates/extras/team_retrieve.html +0 -2
  628. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  629. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
  630. nautobot/ipam/templates/ipam/prefix.html +0 -2
  631. nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
  632. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
  633. nautobot/ipam/templates/ipam/rir.html +0 -2
  634. nautobot/ipam/templates/ipam/routetarget.html +0 -1
  635. nautobot/ipam/templates/ipam/service.html +0 -2
  636. nautobot/ipam/templates/ipam/service_edit.html +0 -2
  637. nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
  638. nautobot/ipam/templates/ipam/vlan.html +0 -2
  639. nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
  640. nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
  641. nautobot/ipam/templates/ipam/vlangroup.html +0 -2
  642. nautobot/ipam/templates/ipam/vrf.html +0 -1
  643. nautobot/tenancy/templates/tenancy/tenant.html +0 -2
  644. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
  645. nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
  646. nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
  648. nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
  651. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
  652. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
  653. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
  654. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
  655. /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
  656. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  657. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
  658. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
  659. {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
@@ -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",
@@ -13,7 +13,8 @@ def update_data_validation_engine_job_module_name(apps, schema_editor):
13
13
  """
14
14
  Job = apps.get_model("extras", "Job")
15
15
  dve_jobs = Job.objects.filter(module_name="nautobot_data_validation_engine.jobs")
16
- dve_jobs.update(module_name="nautobot.data_validation.jobs")
16
+ # Now that the DVE jobs are system jobs, they should be enabled by default
17
+ dve_jobs.update(module_name="nautobot.core.jobs", enabled=True)
17
18
 
18
19
 
19
20
  def update_data_validation_engine_git_repo_contents(apps, schema_editor):
@@ -136,9 +137,6 @@ def copy_app_data_to_core_data(apps, schema_editor):
136
137
  f"""\
137
138
  INSERT INTO data_validation_datacompliance (
138
139
  id,
139
- created,
140
- last_updated,
141
- _custom_field_data,
142
140
  compliance_class_name,
143
141
  last_validation_date,
144
142
  content_type_id,
@@ -150,9 +148,6 @@ INSERT INTO data_validation_datacompliance (
150
148
  message
151
149
  ) SELECT
152
150
  id,
153
- created,
154
- last_updated,
155
- _custom_field_data,
156
151
  compliance_class_name,
157
152
  last_validation_date,
158
153
  content_type_id,
@@ -176,7 +171,7 @@ def revert_data_validation_engine_job_module_name(apps, schema_editor):
176
171
  Revert the `module_name` for the Jobs to match the old location of the data validation engine.
177
172
  """
178
173
  Job = apps.get_model("extras", "Job")
179
- dve_jobs = Job.objects.filter(module_name="nautobot.data_validation.jobs")
174
+ dve_jobs = Job.objects.filter(module_name="nautobot.core.jobs")
180
175
  dve_jobs.update(module_name="nautobot_data_validation_engine.jobs")
181
176
 
182
177
 
@@ -274,9 +269,6 @@ def copy_core_data_to_app_data(apps, schema_editor):
274
269
  f"""\
275
270
  INSERT INTO nautobot_data_validation_engine_datacompliance (
276
271
  id,
277
- created,
278
- last_updated,
279
- _custom_field_data,
280
272
  compliance_class_name,
281
273
  last_validation_date,
282
274
  content_type_id,
@@ -288,9 +280,6 @@ INSERT INTO nautobot_data_validation_engine_datacompliance (
288
280
  message
289
281
  ) SELECT
290
282
  id,
291
- created,
292
- last_updated,
293
- _custom_field_data,
294
283
  compliance_class_name,
295
284
  last_validation_date,
296
285
  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",),
@@ -3,6 +3,7 @@ Model test cases
3
3
  """
4
4
 
5
5
  import re
6
+ from unittest import TestCase
6
7
 
7
8
  from django.contrib.contenttypes.models import ContentType
8
9
  from django.core.validators import ValidationError
@@ -376,3 +377,17 @@ class UniqueValidationRuleModelTestCase(ValidationRuleModelTestCases.ValidationR
376
377
  )
377
378
 
378
379
  rule.clean()
380
+
381
+
382
+ class ValidationRuleModelMixinTestCase(TestCase):
383
+ """
384
+ Validate ValidationRuleModelMixin is working as intended.
385
+ """
386
+
387
+ def test_is_data_compliance_model(self):
388
+ """Validate is_data_compliance_model is set correctly on models using ValidationRuleModelMixin."""
389
+ # These models should have is_data_compliance_model = False
390
+ self.assertFalse(MinMaxValidationRule.is_data_compliance_model)
391
+ self.assertFalse(RegularExpressionValidationRule.is_data_compliance_model)
392
+ self.assertFalse(RequiredValidationRule.is_data_compliance_model)
393
+ self.assertFalse(UniqueValidationRule.is_data_compliance_model)