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
@@ -11,9 +11,10 @@ from django.contrib.contenttypes.models import ContentType
11
11
  from django.core.exceptions import ObjectDoesNotExist
12
12
  from django.db.models import ForeignKey, Model
13
13
  from django.urls import get_resolver, resolve, reverse, URLPattern, URLResolver
14
- from django.utils.module_loading import import_string
15
14
  from django.views.generic.base import RedirectView
16
15
 
16
+ from nautobot.core.utils.module_loading import import_string_optional
17
+
17
18
 
18
19
  def resolve_attr(obj, dotted_field):
19
20
  """
@@ -60,6 +61,14 @@ def resolve_attr(obj, dotted_field):
60
61
  return str(val) if val else None
61
62
 
62
63
 
64
+ def get_breadcrumbs_for_model(model, view_type: str = "List"):
65
+ """Get a UI Component Framework 'Breadcrumbs' instance for the given model's related UIViewSet or generic view."""
66
+ view = get_view_for_model(model)
67
+ if hasattr(view, "get_breadcrumbs"):
68
+ return view.get_breadcrumbs(model, view_type=view_type)
69
+ return None
70
+
71
+
63
72
  def get_changes_for_model(model):
64
73
  """
65
74
  Return a queryset of ObjectChanges for a model or instance. The queryset will be filtered
@@ -77,6 +86,30 @@ def get_changes_for_model(model):
77
86
  raise TypeError(f"{model!r} is not a Django Model class or instance")
78
87
 
79
88
 
89
+ def get_detail_view_components_context_for_model(model) -> dict:
90
+ """Helper method for DistinctViewTabs etc. to retrieve the UI Component Framework context for the base detail view.
91
+
92
+ Functionally equivalent to calling `get_breadcrumbs_for_model()`, `get_object_detail_content_for_model()`, and
93
+ `get_view_titles_for_model()`, but marginally more efficient.
94
+ """
95
+ context = {
96
+ "breadcrumbs": None,
97
+ "object_detail_content": None,
98
+ "view_titles": None,
99
+ }
100
+
101
+ view = get_view_for_model(model, view_type="")
102
+ if view is not None:
103
+ if hasattr(view, "get_breadcrumbs"):
104
+ context["breadcrumbs"] = view.get_breadcrumbs(model, view_type="")
105
+ if hasattr(view, "get_view_titles"):
106
+ context["view_titles"] = view.get_view_titles(model, view_type="")
107
+ if hasattr(view, "object_detail_content"):
108
+ context["object_detail_content"] = view.object_detail_content
109
+
110
+ return context
111
+
112
+
80
113
  def get_model_from_name(model_name):
81
114
  """Given a full model name in dotted format (example: `dcim.model`), a model class is returned if valid.
82
115
 
@@ -181,13 +214,7 @@ def get_related_class_for_model(model, module_name, object_suffix):
181
214
  object_name = f"{model.__name__}{object_suffix}"
182
215
  object_path = f"{app_config.name}.{module_name}.{object_name}"
183
216
 
184
- try:
185
- return import_string(object_path)
186
- # The name of the module is not correct or unable to find the desired object for this model
187
- except (AttributeError, ImportError, ModuleNotFoundError):
188
- pass
189
-
190
- return None
217
+ return import_string_optional(object_path)
191
218
 
192
219
 
193
220
  def get_filterset_for_model(model):
@@ -227,6 +254,12 @@ def get_form_for_model(model, form_prefix=""):
227
254
  return get_related_class_for_model(model, module_name="forms", object_suffix=object_suffix)
228
255
 
229
256
 
257
+ def get_object_detail_content_for_model(model):
258
+ """Get the UI Component Framework 'object_detail_content' for the given model's related UIViewSet or ObjectView."""
259
+ view = get_view_for_model(model)
260
+ return getattr(view, "object_detail_content", None)
261
+
262
+
230
263
  def get_related_field_for_models(from_model, to_model):
231
264
  """
232
265
  Find the field on `from_model` that is a relation to `to_model`.
@@ -294,6 +327,14 @@ def get_view_for_model(model, view_type=""):
294
327
  return result
295
328
 
296
329
 
330
+ def get_view_titles_for_model(model, view_type: str = "List"):
331
+ """Get a UI Component Framework 'Titles' instance for the given model's related UIViewSet or generic view."""
332
+ view = get_view_for_model(model)
333
+ if hasattr(view, "get_view_titles"):
334
+ return view.get_view_titles(model, view_type=view_type)
335
+ return None
336
+
337
+
297
338
  def get_model_for_view_name(view_name):
298
339
  """
299
340
  Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
@@ -7,9 +7,30 @@ import os
7
7
  import pkgutil
8
8
  import sys
9
9
 
10
+ from django.utils.module_loading import import_string
11
+
10
12
  logger = logging.getLogger(__name__)
11
13
 
12
14
 
15
+ def import_string_optional(dotted_path):
16
+ """An extension/wrapper of Django's `import_string()` that returns `None` if no such dotted path exists."""
17
+ try:
18
+ return import_string(dotted_path)
19
+ except ModuleNotFoundError as err:
20
+ # No such module
21
+ module_name, _ = dotted_path.rsplit(".", 1)
22
+ if module_name.startswith(err.name): # tried to import foo.bar.baz but couldn't find foo.bar, etc.
23
+ return None
24
+ # Some import *from within* the given module couldn't find what it was looking for?
25
+ raise
26
+ except ImportError as err:
27
+ if "does not define" in str(err):
28
+ # Exception raised by Django if the module exists but has no such attribute
29
+ return None
30
+ # Maybe a legitimate problem with the import?
31
+ raise
32
+
33
+
13
34
  @contextmanager
14
35
  def _temporarily_add_to_sys_path(path):
15
36
  """
@@ -0,0 +1,128 @@
1
+ from django.core.exceptions import FieldDoesNotExist
2
+ from django.db import router, transaction
3
+ from django.db.utils import IntegrityError
4
+ from social_core.exceptions import AuthAlreadyAssociated
5
+
6
+ """
7
+ Social Auth Account Takeover Vulnerability Patch
8
+ =================================================
9
+
10
+ This module patches CVE-2025-61783, a medium security vulnerability in social_django that allows
11
+ account takeover when using OAuth providers that don't verify email addresses.
12
+
13
+ VULNERABILITY OVERVIEW
14
+ ----------------------
15
+ The vulnerability exists in social_django.storage.DjangoUserMixin.create_user(),
16
+ specifically in how it handles IntegrityError exceptions. When user creation fails due to
17
+ a duplicate email or username, the original code catches the IntegrityError and blindly
18
+ retrieves an existing user via manager.get(), returning that user without verifying that
19
+ a social auth association exists for the provider/UID combination.
20
+
21
+ PATCHING STRATEGY
22
+ -----------------
23
+ This implementation patches the `create_user` method on the `user` class property of
24
+ DjangoStorage, which is where the vulnerability manifests. The patch changes the behavior
25
+ to raise AuthAlreadyAssociated when an IntegrityError occurs, preventing the silent
26
+ return of an existing user.
27
+
28
+ By patching at this level, we:
29
+ - Maintain compatibility with custom pipelines
30
+ - Don't require changes to user's social auth configuration
31
+ - Apply the fix exactly where the vulnerability occurs
32
+ - Preserve all other social auth functionality
33
+
34
+ REMOVAL
35
+ -------
36
+ Remove this patch when upgrading to social-auth-app-django >= 5.6.0
37
+ (version that includes PR #803 merged into the main branch).
38
+
39
+ To verify if you still need the patch:
40
+ pip show social-auth-app-django
41
+ # Check version against PR #803 merge status
42
+
43
+ REFERENCES
44
+ ----------
45
+ - Vulnerability Report: https://github.com/python-social-auth/social-app-django/security/advisories/GHSA-wv4w-6qv2-qqfg
46
+ - Original Issue: https://github.com/python-social-auth/social-app-django/issues/220
47
+ - Official Fix PR: https://github.com/python-social-auth/social-app-django/pull/803
48
+
49
+ SECURITY NOTICE
50
+ ---------------
51
+ This patch addresses a MEDIUM security vulnerability
52
+
53
+ Disabling this patch without mitigation will expose your application to account
54
+ takeover attacks.
55
+
56
+ AUTHOR & MAINTENANCE
57
+ --------------------
58
+ Patch implemented as temporary security measure for Nautobot deployment.
59
+ As picking up the latest social_django would require a major version upgrade of Django,
60
+ which itself would require a breaking change to the Nautobot configuration, this patch
61
+ is intended to be a stopgap until such time as Nautobot can upgrade to a version of
62
+ social_django that includes the fix.
63
+ """
64
+
65
+
66
+ def patch_django_storage(original_django_storage):
67
+ """
68
+ Apply security patch to DjangoStorage.user.create_user method.
69
+
70
+ This patches the vulnerability in python-social-auth where create_user
71
+ catches IntegrityError and blindly returns an existing user, enabling
72
+ account takeover via unverified OAuth providers.
73
+
74
+ Args:
75
+ storage_class (DjangoStorage): The original DjangoStorage class to patch.
76
+
77
+ Returns:
78
+ None
79
+
80
+ Note:
81
+ The patch is a nearly verbatim copy of the original create_user method
82
+ from social_django.storage.DjangoUserMixin from 5.4.3, except that it
83
+ adopts the fail-closed change described in
84
+ https://github.com/python-social-auth/social-app-django/pull/803
85
+
86
+ The modified lines are called out with "Patched logic" comments below.
87
+ """
88
+
89
+ def patched_create_user(cls, *args, **kwargs):
90
+ username_field = cls.username_field()
91
+ if "username" in kwargs:
92
+ if username_field not in kwargs:
93
+ kwargs[username_field] = kwargs.pop("username")
94
+ else:
95
+ # If username_field is 'email' and there is no field named "username"
96
+ # then latest should be removed from kwargs.
97
+ try:
98
+ cls.user_model()._meta.get_field("username")
99
+ except FieldDoesNotExist:
100
+ kwargs.pop("username")
101
+ try:
102
+ if hasattr(transaction, "atomic"):
103
+ # In Django versions that have an "atomic" transaction decorator / context
104
+ # manager, there's a transaction wrapped around this call.
105
+ # If the create fails below due to an IntegrityError, ensure that the transaction
106
+ # stays undamaged by wrapping the create in an atomic.
107
+ using = router.db_for_write(cls.user_model())
108
+ with transaction.atomic(using=using):
109
+ user = cls.user_model()._default_manager.create_user(*args, **kwargs)
110
+ else:
111
+ user = cls.user_model()._default_manager.create_user(*args, **kwargs)
112
+ except IntegrityError as exc:
113
+ # ORIGINAL CODE BELOW:
114
+ # # If email comes in as None it won't get found in the get
115
+ # if kwargs.get("email", True) is None:
116
+ # kwargs["email"] = ""
117
+ # try:
118
+ # user = cls.user_model()._default_manager.get(*args, **kwargs)
119
+ # except cls.user_model().DoesNotExist:
120
+ # raise exc
121
+
122
+ # BEGIN Patched logic
123
+ raise AuthAlreadyAssociated(None) from exc
124
+ # END Patched logic
125
+ return user
126
+
127
+ # Apply the patch to the original DjangoStorage.user.create_user method
128
+ original_django_storage.user.create_user = classmethod(patched_create_user)
@@ -1,6 +1,8 @@
1
1
  import contextlib
2
2
  import datetime
3
+ from importlib import resources
3
4
  import logging
5
+ import mimetypes
4
6
  import os
5
7
  import platform
6
8
  import posixpath
@@ -16,7 +18,7 @@ from django.contrib.auth.decorators import permission_required
16
18
  from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
17
19
  from django.contrib.contenttypes.models import ContentType
18
20
  from django.core.cache import cache
19
- from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
21
+ from django.http import FileResponse, HttpResponseForbidden, HttpResponseServerError, JsonResponse
20
22
  from django.shortcuts import get_object_or_404, render
21
23
  from django.template import loader, RequestContext, Template
22
24
  from django.template.exceptions import TemplateDoesNotExist
@@ -61,6 +63,7 @@ from nautobot.core.views.utils import (
61
63
  )
62
64
  from nautobot.extras.forms import GraphQLQueryForm
63
65
  from nautobot.extras.models import FileProxy, GraphQLQuery, Status
66
+ from nautobot.extras.plugins.urls import BASE_URL_TO_APP_LABEL
64
67
  from nautobot.extras.registry import registry
65
68
  from nautobot.extras.tables import StatusTable
66
69
 
@@ -144,6 +147,40 @@ class HomeView(AccessMixin, TemplateView):
144
147
  return self.render_to_response(context)
145
148
 
146
149
 
150
+ class AppDocsView(LoginRequiredMixin, View):
151
+ """
152
+ Serve documentation files for any pip-installed app from inside the package,
153
+ only for authenticated users.
154
+ """
155
+
156
+ def get(self, request, app_base_url, path="index.html"):
157
+ app_label = BASE_URL_TO_APP_LABEL.get(app_base_url)
158
+ if not app_label:
159
+ return JsonResponse({"detail": f"Unknown base_url '{app_base_url}'."}, status=404)
160
+ try:
161
+ base_dir = resources.files(app_label)
162
+ except ModuleNotFoundError:
163
+ return JsonResponse({"detail": f"App {app_label} not found."}, status=404)
164
+
165
+ # Dir to documentation inside the package
166
+ docs_dir = base_dir / "docs"
167
+ # Normalize path to avoid (../) etc.
168
+ normalized_path = posixpath.normpath(path).lstrip("/")
169
+ file_path = docs_dir / normalized_path
170
+
171
+ # Additional check to ensure the resolved path is still within docs_dir
172
+ if not file_path.resolve().is_relative_to(docs_dir.resolve()):
173
+ return JsonResponse({"detail": "Access denied."}, status=403)
174
+
175
+ if not file_path.is_file():
176
+ return JsonResponse({"detail": f"File {file_path} not found."}, status=404)
177
+
178
+ # Determine the MIME type based on the file extension and return the file as an HTTP response.
179
+ # This ensures that browsers interpret the file correctly (e.g., HTML, CSS, JS, images).
180
+ content_type, _ = mimetypes.guess_type(str(file_path))
181
+ return FileResponse(open(file_path, "rb"), content_type=content_type)
182
+
183
+
147
184
  class MediaView(AccessMixin, View):
148
185
  """
149
186
  Serves media files while enforcing login restrictions.
@@ -587,7 +587,7 @@ class ObjectDeleteView(UIComponentsMixin, GetReturnURLMixin, ObjectPermissionReq
587
587
  """
588
588
 
589
589
  queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
590
- template_name = "generic/object_delete.html"
590
+ template_name = "generic/object_destroy.html"
591
591
 
592
592
  def get_required_permission(self):
593
593
  return get_permission_for_model(self.queryset.model, "delete")
@@ -1044,7 +1044,7 @@ class BulkEditView(
1044
1044
  filterset: Optional[type[FilterSet]] = None
1045
1045
  table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
1046
1046
  form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1047
- template_name = "generic/object_bulk_edit.html"
1047
+ template_name = "generic/object_bulk_update.html"
1048
1048
 
1049
1049
  def get_required_permission(self):
1050
1050
  return get_permission_for_model(self.queryset.model, "change")
@@ -1246,7 +1246,7 @@ class BulkDeleteView(
1246
1246
  filterset: Optional[type[FilterSet]] = None
1247
1247
  table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
1248
1248
  form: Optional[type[Form]] = None
1249
- template_name = "generic/object_bulk_delete.html"
1249
+ template_name = "generic/object_bulk_destroy.html"
1250
1250
 
1251
1251
  def get_required_permission(self):
1252
1252
  return get_permission_for_model(self.queryset.model, "delete")
@@ -73,6 +73,7 @@ PERMISSIONS_ACTION_MAP = {
73
73
  "bulk_update": "change",
74
74
  "changelog": "view",
75
75
  "notes": "view",
76
+ "data_compliance": "view",
76
77
  "approve": "change",
77
78
  "deny": "change",
78
79
  }
@@ -262,52 +263,57 @@ class UIComponentsMixin:
262
263
  breadcrumbs: ClassVar[Optional[Breadcrumbs]] = None
263
264
  view_titles: ClassVar[Optional[Titles]] = None
264
265
 
265
- def get_view_titles(self, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List") -> Titles:
266
+ @classmethod
267
+ def get_view_titles(cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List") -> Titles:
266
268
  """
267
269
  Resolve and return the `Titles` component instance.
268
270
 
269
271
  Resolution order:
270
- 1) If `self.view_titles` is set on the current view, use it.
271
- 2) Else, if `model` is provided, copy the `view_titles` from the view
272
- class associated with that model via `lookup.get_view_for_model(model, action)`.
272
+ 1) If `.view_titles` is set on the current view, use it.
273
+ 2) Else, if `model` is provided, copy the `view_titles` from the view class associated with that model
274
+ via `lookup.get_view_for_model(model, action)`.
273
275
  3) Else, instantiate and return the default `Titles()`.
274
276
 
275
277
  Args:
276
278
  model: A Django model **class**, **instance**, dotted name string, or `None`.
277
279
  Passed to `lookup.get_view_for_model()` to find the related view class.
278
280
  If `None`, only local/default resolution is used.
279
- view_type: Logical view type used by `lookup.get_view_for_model()` (e.g., `"List"` or empty to construct `"DeviceView"` string).
281
+ view_type: Logical view type used by `lookup.get_view_for_model()`
282
+ (e.g., `"List"` or empty to construct `"DeviceView"` string).
280
283
 
281
284
  Returns:
282
285
  Titles: A concrete `Titles` component instance ready to use.
283
286
  """
284
- return self._resolve_component("view_titles", Titles, model, view_type)
287
+ return cls._resolve_component("view_titles", Titles, model, view_type)
285
288
 
289
+ @classmethod
286
290
  def get_breadcrumbs(
287
- self, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List"
291
+ cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List"
288
292
  ) -> Breadcrumbs:
289
293
  """
290
294
  Resolve and return the `Breadcrumbs` component instance.
291
295
 
292
296
  Resolution order mirrors `get_view_titles()`:
293
- 1) Use `self.breadcrumbs` if set locally.
294
- 2) Else, if `model` is provided, copy the `breadcrumbs` from the view
295
- class associated with that model via `lookup.get_view_for_model(model, action)`.
297
+ 1) Use `.breadcrumbs` if set locally.
298
+ 2) Else, if `model` is provided, copy the `breadcrumbs` from the view class associated with that model
299
+ via `lookup.get_view_for_model(model, action)`.
296
300
  3) Else return a new default `Breadcrumbs()`.
297
301
 
298
302
  Args:
299
303
  model: A Django model **class**, **instance**, dotted name string, or `None`.
300
304
  Passed to `lookup.get_view_for_model()` to find the related view class.
301
305
  If `None`, only local/default resolution is used.
302
- view_type: Logical view type used by `lookup.get_view_for_model()` (e.g., `"List"` or empty to construct `"DeviceView"` string).
306
+ view_type: Logical view type used by `lookup.get_view_for_model()`
307
+ (e.g., `"List"` or empty to construct `"DeviceView"` string).
303
308
 
304
309
  Returns:
305
310
  Breadcrumbs: A concrete `Breadcrumbs` component instance.
306
311
  """
307
- return self._resolve_component("breadcrumbs", Breadcrumbs, model, view_type)
312
+ return cls._resolve_component("breadcrumbs", Breadcrumbs, model, view_type)
308
313
 
314
+ @classmethod
309
315
  def _resolve_component(
310
- self,
316
+ cls,
311
317
  attr_name: str,
312
318
  default_cls: Type[Union[Breadcrumbs, Titles]],
313
319
  model: Union[None, str, Type[Model], Model] = None,
@@ -328,14 +334,14 @@ class UIComponentsMixin:
328
334
  Returns:
329
335
  Breadcrumbs/Title instance.
330
336
  """
331
- local = getattr(self, attr_name, None)
337
+ local = getattr(cls, attr_name, None)
332
338
  if local is not None:
333
- return self._instantiate_if_needed(local, default_cls)
339
+ return cls._instantiate_if_needed(local, default_cls)
334
340
 
335
341
  if model is not None:
336
342
  view_class = lookup.get_view_for_model(model, view_type)
337
343
  view_component = getattr(view_class, attr_name, None)
338
- return self._instantiate_if_needed(view_component, default_cls)
344
+ return cls._instantiate_if_needed(view_component, default_cls)
339
345
 
340
346
  return default_cls()
341
347
 
@@ -532,7 +538,8 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
532
538
  form.add_error(None, msg)
533
539
  return form
534
540
 
535
- def _handle_not_implemented_error(self):
541
+ def _handle_not_implemented_error(self, error):
542
+ self.logger.debug(f"NotImplementedError raised on action {self.action} resulting in error: {error}")
536
543
  # Blanket handler for NotImplementedError raised by form helper functions
537
544
  msg = "Please provide the appropriate mixin before using this helper function"
538
545
  messages.error(self.request, msg)
@@ -567,8 +574,8 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
567
574
  self._handle_validation_error(e)
568
575
  except ObjectDoesNotExist:
569
576
  form = self._handle_object_does_not_exist(form)
570
- except NotImplementedError:
571
- self._handle_not_implemented_error()
577
+ except NotImplementedError as error:
578
+ self._handle_not_implemented_error(error)
572
579
 
573
580
  if not self.has_error:
574
581
  self.logger.debug("Form validation was successful")
@@ -730,7 +737,11 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
730
737
  except TemplateDoesNotExist:
731
738
  # Try a different detail view template format
732
739
  template_name = f"{app_label}/{model_opts.model_name}.html"
733
- select_template([template_name])
740
+ try:
741
+ select_template([template_name])
742
+ except TemplateDoesNotExist:
743
+ # Catch-all fallback to just object_retrieve.html
744
+ template_name = "generic/object_retrieve.html"
734
745
  return template_name
735
746
 
736
747
  def get_form(self, *args, **kwargs):
@@ -1498,7 +1509,9 @@ class ObjectChangeLogViewMixin(NautobotViewSetMixin):
1498
1509
 
1499
1510
  base_template: Optional[str] = None
1500
1511
 
1501
- @drf_action(detail=True)
1512
+ @drf_action(
1513
+ detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_objectchange"]
1514
+ )
1502
1515
  def changelog(self, request, *args, **kwargs):
1503
1516
  model = self.get_queryset().model
1504
1517
  data = {
@@ -1519,7 +1532,7 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
1519
1532
 
1520
1533
  base_template: Optional[str] = None
1521
1534
 
1522
- @drf_action(detail=True)
1535
+ @drf_action(detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_note"])
1523
1536
  def notes(self, request, *args, **kwargs):
1524
1537
  model = self.get_queryset().model
1525
1538
  data = {
@@ -1527,3 +1540,13 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
1527
1540
  "active_tab": "notes",
1528
1541
  }
1529
1542
  return Response(data)
1543
+
1544
+
1545
+ class ObjectDataComplianceViewMixin(NautobotViewSetMixin):
1546
+ """
1547
+ UI Mixin for a DataCompliance to show up for a given object.
1548
+ """
1549
+
1550
+ @drf_action(detail=True, url_path="data-compliance")
1551
+ def data_compliance(self, request, *args, **kwargs):
1552
+ return Response({})
@@ -15,7 +15,7 @@ from nautobot.core.forms import (
15
15
  TableConfigForm,
16
16
  )
17
17
  from nautobot.core.forms.forms import DynamicFilterFormSet
18
- from nautobot.core.templatetags.helpers import bettertitle, validated_viewname
18
+ from nautobot.core.templatetags.helpers import validated_viewname
19
19
  from nautobot.core.utils.config import get_settings_or_config
20
20
  from nautobot.core.utils.permissions import get_permission_for_model
21
21
  from nautobot.core.utils.requests import (
@@ -227,7 +227,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
227
227
  if view.filterset is not None:
228
228
  filterset_filters = view.filterset.filters
229
229
  else:
230
- filterset_filters = view.filterset_class.get_filters()
230
+ filterset_filters = view.filterset_class.base_filters
231
231
  display_filter_params = [
232
232
  check_filter_for_display(filterset_filters, field_name, values)
233
233
  for field_name, values in view.filter_params.items()
@@ -324,7 +324,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
324
324
  "action_buttons": valid_actions,
325
325
  "list_url": list_url,
326
326
  "saved_views": saved_views,
327
- "title": bettertitle(model._meta.verbose_name_plural),
328
327
  }
329
328
  )
330
329
  elif view.action in ["create", "update"]:
@@ -365,6 +364,8 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
365
364
  # See form_valid() for self.action == "bulk_create".
366
365
  self.template = data.get("template", view.get_template_name())
367
366
 
367
+ data["request"] = request
368
+
368
369
  return super().render(data, accepted_media_type=accepted_media_type, renderer_context=renderer_context)
369
370
 
370
371
  @staticmethod
@@ -11,9 +11,10 @@ class NautobotUIViewSet(
11
11
  mixins.ObjectBulkUpdateViewMixin,
12
12
  mixins.ObjectChangeLogViewMixin,
13
13
  mixins.ObjectNotesViewMixin,
14
+ mixins.ObjectDataComplianceViewMixin,
14
15
  ):
15
16
  """
16
17
  Nautobot BaseViewSet that is intended for UI use only. It provides default Nautobot functionalities such as
17
18
  `create()`, `update()`, `partial_update()`, `bulk_update()`, `destroy()`, `bulk_destroy()`, `retrieve()`
18
- `notes()`, `changelog()` and `list()` actions.
19
+ `notes()`, `changelog()`, `list()`, and `data_compliance()` actions.
19
20
  """
@@ -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