nautobot 2.4.21__py3-none-any.whl → 3.0.0a3__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (919) hide show
  1. nautobot/apps/choices.py +2 -2
  2. nautobot/apps/filters.py +9 -9
  3. nautobot/apps/forms.py +2 -0
  4. nautobot/apps/models.py +7 -2
  5. nautobot/apps/ui.py +20 -1
  6. nautobot/apps/utils.py +2 -3
  7. nautobot/apps/views.py +7 -1
  8. nautobot/circuits/filters.py +8 -23
  9. nautobot/circuits/navigation.py +3 -1
  10. nautobot/circuits/templates/circuits/circuit_create.html +9 -9
  11. nautobot/circuits/templates/circuits/circuit_terminations_swap.html +2 -2
  12. nautobot/circuits/templates/circuits/circuittermination_create.html +24 -33
  13. nautobot/circuits/templates/circuits/inc/circuit_termination.html +10 -10
  14. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +13 -13
  15. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +6 -6
  16. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +3 -3
  17. nautobot/circuits/templates/circuits/inc/speed_widget.html +13 -13
  18. nautobot/circuits/templates/circuits/provider_create.html +9 -9
  19. nautobot/circuits/tests/integration/test_circuit.py +19 -19
  20. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +3 -0
  21. nautobot/circuits/tests/integration/test_relationships.py +4 -12
  22. nautobot/circuits/views.py +0 -2
  23. nautobot/cloud/filters.py +1 -13
  24. nautobot/cloud/navigation.py +3 -1
  25. nautobot/cloud/templates/cloud/cloudnetwork_update.html +9 -9
  26. nautobot/cloud/templates/cloud/cloudservice_update.html +6 -6
  27. nautobot/core/api/fields.py +30 -2
  28. nautobot/core/api/schema.py +1 -1
  29. nautobot/core/api/serializers.py +9 -2
  30. nautobot/core/api/urls.py +2 -0
  31. nautobot/core/api/views.py +58 -37
  32. nautobot/core/apps/__init__.py +6 -12
  33. nautobot/core/branching.py +83 -0
  34. nautobot/core/celery/__init__.py +11 -6
  35. nautobot/core/celery/backends.py +2 -0
  36. nautobot/core/celery/encoders.py +7 -0
  37. nautobot/core/celery/task.py +44 -0
  38. nautobot/core/checks.py +60 -0
  39. nautobot/core/cli/bootstrap_v3_to_v5.py +776 -0
  40. nautobot/core/constants.py +9 -0
  41. nautobot/core/context_processors.py +84 -0
  42. nautobot/core/filters.py +131 -2
  43. nautobot/core/forms/__init__.py +4 -2
  44. nautobot/core/forms/fields.py +10 -8
  45. nautobot/core/forms/forms.py +21 -9
  46. nautobot/core/forms/search.py +0 -15
  47. nautobot/core/forms/widgets.py +3 -2
  48. nautobot/core/graphql/__init__.py +8 -26
  49. nautobot/core/graphql/generators.py +16 -6
  50. nautobot/core/graphql/schema.py +1 -1
  51. nautobot/core/graphql/schema_init.py +1 -2
  52. nautobot/core/graphql/utils.py +7 -9
  53. nautobot/core/jobs/__init__.py +158 -0
  54. nautobot/core/management/commands/generate_test_data.py +28 -9
  55. nautobot/core/models/__init__.py +17 -2
  56. nautobot/core/models/fields.py +3 -2
  57. nautobot/core/models/generics.py +9 -1
  58. nautobot/core/models/name_color_content_types.py +1 -1
  59. nautobot/core/models/ordering.py +7 -5
  60. nautobot/core/models/querysets.py +77 -2
  61. nautobot/core/models/tree_queries.py +6 -4
  62. nautobot/core/settings.py +30 -16
  63. nautobot/core/settings.yaml +13 -7
  64. nautobot/core/tables.py +114 -44
  65. nautobot/core/templates/403.html +1 -1
  66. nautobot/core/templates/403_csrf_failure.html +1 -1
  67. nautobot/core/templates/404.html +1 -1
  68. nautobot/core/templates/40x.html +8 -8
  69. nautobot/core/templates/500.html +10 -10
  70. nautobot/core/templates/about.html +13 -12
  71. nautobot/core/templates/admin/actions.html +1 -1
  72. nautobot/core/templates/admin/app_index.html +3 -3
  73. nautobot/core/templates/admin/base.html +45 -52
  74. nautobot/core/templates/admin/base_site.html +0 -9
  75. nautobot/core/templates/admin/change_form.html +5 -5
  76. nautobot/core/templates/admin/change_list.html +8 -12
  77. nautobot/core/templates/admin/change_list_results.html +3 -3
  78. nautobot/core/templates/admin/config/config.html +24 -24
  79. nautobot/core/templates/admin/delete_confirmation.html +5 -5
  80. nautobot/core/templates/admin/edit_inline/stacked.html +5 -5
  81. nautobot/core/templates/admin/edit_inline/tabular.html +3 -3
  82. nautobot/core/templates/admin/includes/fieldset.html +15 -15
  83. nautobot/core/templates/admin/index.html +8 -8
  84. nautobot/core/templates/admin/submit_line.html +5 -5
  85. nautobot/core/templates/base_django.html +36 -32
  86. nautobot/core/templates/buttons/add.html +1 -1
  87. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +2 -2
  88. nautobot/core/templates/buttons/export.html +17 -18
  89. nautobot/core/templates/buttons/job_import.html +2 -2
  90. nautobot/core/templates/components/breadcrumbs.html +19 -17
  91. nautobot/core/templates/components/button/dropdown.html +7 -5
  92. nautobot/core/templates/components/echarts.html +2 -0
  93. nautobot/core/templates/components/layout/one_over_two.html +3 -3
  94. nautobot/core/templates/components/layout/two_over_one.html +3 -3
  95. nautobot/core/templates/components/panel/body_content_data_table.html +2 -2
  96. nautobot/core/templates/components/panel/body_content_tags.html +1 -1
  97. nautobot/core/templates/components/panel/body_wrapper_generic.html +4 -2
  98. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  99. nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +5 -3
  100. nautobot/core/templates/components/panel/body_wrapper_table.html +4 -2
  101. nautobot/core/templates/components/panel/footer_contacts_table.html +4 -4
  102. nautobot/core/templates/components/panel/footer_content_table.html +3 -3
  103. nautobot/core/templates/components/panel/grouping_toggle.html +12 -11
  104. nautobot/core/templates/components/panel/header_extra_content_table.html +2 -11
  105. nautobot/core/templates/components/panel/panel.html +6 -3
  106. nautobot/core/templates/components/panel/stats_panel_body.html +9 -7
  107. nautobot/core/templates/components/tab/content_wrapper.html +29 -1
  108. nautobot/core/templates/components/tab/label_wrapper.html +10 -2
  109. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +11 -4
  110. nautobot/core/templates/echarts/echarts.html +20 -0
  111. nautobot/core/templates/exceptions/import_error.html +2 -2
  112. nautobot/core/templates/exceptions/permission_error.html +1 -1
  113. nautobot/core/templates/exceptions/programming_error.html +2 -2
  114. nautobot/core/templates/generic/object_bulk_add_component.html +29 -20
  115. nautobot/core/templates/generic/object_bulk_create.html +87 -75
  116. nautobot/core/templates/generic/object_bulk_destroy.html +35 -37
  117. nautobot/core/templates/generic/object_bulk_remove.html +30 -26
  118. nautobot/core/templates/generic/object_bulk_rename.html +53 -40
  119. nautobot/core/templates/generic/object_bulk_update.html +36 -29
  120. nautobot/core/templates/generic/object_create.html +40 -27
  121. nautobot/core/templates/generic/object_import.html +36 -24
  122. nautobot/core/templates/generic/object_list.html +279 -215
  123. nautobot/core/templates/generic/object_notes.html +21 -11
  124. nautobot/core/templates/generic/object_retrieve.html +161 -213
  125. nautobot/core/templates/graphene/graphiql.html +113 -60
  126. nautobot/core/templates/home.html +164 -87
  127. nautobot/core/templates/import_success.html +3 -2
  128. nautobot/core/templates/inc/ajax_loader.html +1 -1
  129. nautobot/core/templates/inc/computed_fields/panel_data.html +25 -13
  130. nautobot/core/templates/inc/created_updated.html +12 -7
  131. nautobot/core/templates/inc/custom_fields/panel_data.html +28 -16
  132. nautobot/core/templates/inc/custom_fields_panel.html +3 -3
  133. nautobot/core/templates/inc/dynamic_groups_panel.html +3 -3
  134. nautobot/core/templates/inc/extras_features_edit_form_fields.html +15 -15
  135. nautobot/core/templates/inc/footer.html +90 -40
  136. nautobot/core/templates/inc/form_static_field.html +6 -0
  137. nautobot/core/templates/inc/header.html +75 -0
  138. nautobot/core/templates/inc/header_banners.html +17 -0
  139. nautobot/core/templates/inc/header_messages.html +6 -0
  140. nautobot/core/templates/inc/image_attachments.html +9 -9
  141. nautobot/core/templates/inc/javascript.html +7 -24
  142. nautobot/core/templates/inc/media.html +4 -29
  143. nautobot/core/templates/inc/modal.html +2 -2
  144. nautobot/core/templates/inc/nav_favorites.html +27 -0
  145. nautobot/core/templates/inc/nav_menu.html +150 -108
  146. nautobot/core/templates/inc/object_details_advanced_panel.html +84 -71
  147. nautobot/core/templates/inc/page_title.html +23 -0
  148. nautobot/core/templates/inc/paginator.html +39 -28
  149. nautobot/core/templates/inc/relationships/panel_override.html +3 -3
  150. nautobot/core/templates/inc/relationships_panel.html +3 -3
  151. nautobot/core/templates/inc/relationships_table_rows.html +1 -1
  152. nautobot/core/templates/inc/search_panel.html +22 -16
  153. nautobot/core/templates/inc/table.html +61 -36
  154. nautobot/core/templates/inc/tenancy_form_panel.html +3 -3
  155. nautobot/core/templates/login.html +17 -59
  156. nautobot/core/templates/modals/modal_theme.html +12 -23
  157. nautobot/core/templates/nautobot_config.py.j2 +6 -5
  158. nautobot/core/templates/panel_table.html +8 -12
  159. nautobot/core/templates/redoc_ui.html +80 -0
  160. nautobot/core/templates/rest_framework/api.html +43 -21
  161. nautobot/core/templates/search.html +12 -13
  162. nautobot/core/templates/swagger_ui.html +19 -4
  163. nautobot/core/templates/system_jobs/import_objects.html +70 -58
  164. nautobot/core/templates/template.css +0 -6
  165. nautobot/core/templates/utilities/comment_form.html +34 -0
  166. nautobot/core/templates/utilities/confirmation_form.html +17 -9
  167. nautobot/core/templates/utilities/obj_table.html +19 -11
  168. nautobot/core/templates/utilities/render_field.html +27 -21
  169. nautobot/core/templates/utilities/render_jinja2.html +22 -25
  170. nautobot/core/templates/utilities/templatetags/advanced_filter_indicator.html +8 -0
  171. nautobot/core/templates/utilities/templatetags/badge.html +1 -1
  172. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +2 -3
  173. nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +482 -0
  174. nautobot/core/templates/utilities/templatetags/modal_form_as_dialog.html +14 -18
  175. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +11 -11
  176. nautobot/core/templates/utilities/templatetags/table_config_form.html +51 -24
  177. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  178. nautobot/core/templates/utilities/templatetags/utilization_graph.html +3 -3
  179. nautobot/core/templates/utilities/theme_preview.html +829 -566
  180. nautobot/core/templates/utilities/worker_status.html +42 -41
  181. nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
  182. nautobot/core/templates/widgets/sluginput.html +2 -2
  183. nautobot/core/templatetags/buttons.py +38 -40
  184. nautobot/core/templatetags/helpers.py +105 -28
  185. nautobot/core/templatetags/ui_framework.py +17 -0
  186. nautobot/core/testing/api.py +76 -12
  187. nautobot/core/testing/filters.py +11 -27
  188. nautobot/core/testing/integration.py +128 -10
  189. nautobot/core/testing/mixins.py +7 -4
  190. nautobot/core/testing/utils.py +28 -5
  191. nautobot/core/testing/views.py +125 -27
  192. nautobot/core/tests/integration/test_app_home.py +39 -35
  193. nautobot/core/tests/integration/test_app_navbar.py +60 -67
  194. nautobot/core/tests/integration/test_filters.py +123 -55
  195. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  196. nautobot/core/tests/integration/test_home.py +10 -18
  197. nautobot/core/tests/integration/test_import_objects_ui.py +2 -9
  198. nautobot/core/tests/integration/test_navbar.py +41 -16
  199. nautobot/core/tests/integration/test_swagger.py +1 -7
  200. nautobot/core/tests/integration/test_theme.py +3 -0
  201. nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
  202. nautobot/core/tests/runner.py +6 -1
  203. nautobot/core/tests/test_api.py +5 -3
  204. nautobot/core/tests/test_branching.py +154 -0
  205. nautobot/core/tests/test_breadcrumbs.py +7 -8
  206. nautobot/core/tests/test_checks.py +28 -0
  207. nautobot/core/tests/test_commands.py +0 -41
  208. nautobot/core/tests/test_config.py +2 -1
  209. nautobot/core/tests/test_csv.py +4 -7
  210. nautobot/core/tests/test_filters.py +326 -318
  211. nautobot/core/tests/test_forms.py +19 -30
  212. nautobot/core/tests/test_graphql.py +67 -57
  213. nautobot/core/tests/test_models.py +1 -1
  214. nautobot/core/tests/test_nautobot_server.py +2 -0
  215. nautobot/core/tests/test_navigations.py +78 -10
  216. nautobot/core/tests/test_tables.py +3 -1
  217. nautobot/core/tests/test_templatetags_helpers.py +61 -21
  218. nautobot/core/tests/test_templatetags_ui_framework.py +36 -18
  219. nautobot/core/tests/test_ui.py +207 -2
  220. nautobot/core/tests/test_utils.py +147 -2
  221. nautobot/core/tests/test_views.py +201 -64
  222. nautobot/core/tests/test_views_utils.py +1 -1
  223. nautobot/core/ui/breadcrumbs.py +2 -12
  224. nautobot/core/ui/choices.py +190 -0
  225. nautobot/core/ui/constants.py +86 -0
  226. nautobot/core/ui/echarts.py +474 -0
  227. nautobot/core/ui/nav.py +5 -1
  228. nautobot/core/ui/object_detail.py +180 -16
  229. nautobot/core/urls.py +13 -1
  230. nautobot/core/utils/cache.py +71 -0
  231. nautobot/core/utils/data.py +8 -5
  232. nautobot/core/utils/filtering.py +8 -2
  233. nautobot/core/utils/git.py +3 -3
  234. nautobot/core/utils/lookup.py +87 -13
  235. nautobot/core/utils/migrations.py +22 -0
  236. nautobot/core/utils/module_loading.py +26 -0
  237. nautobot/core/utils/permissions.py +9 -5
  238. nautobot/core/views/__init__.py +114 -63
  239. nautobot/core/views/generic.py +34 -27
  240. nautobot/core/views/mixins.py +49 -27
  241. nautobot/core/views/renderers.py +3 -5
  242. nautobot/core/views/utils.py +10 -5
  243. nautobot/core/views/viewsets.py +2 -1
  244. nautobot/data_validation/__init__.py +0 -0
  245. nautobot/data_validation/api/__init__.py +1 -0
  246. nautobot/data_validation/api/serializers.py +80 -0
  247. nautobot/data_validation/api/urls.py +20 -0
  248. nautobot/data_validation/api/views.py +44 -0
  249. nautobot/data_validation/apps.py +18 -0
  250. nautobot/data_validation/custom_validators.py +330 -0
  251. nautobot/data_validation/filters.py +133 -0
  252. nautobot/data_validation/form_mixin.py +25 -0
  253. nautobot/data_validation/forms.py +342 -0
  254. nautobot/data_validation/migrations/0001_initial.py +224 -0
  255. nautobot/data_validation/migrations/0002_data_migration_from_app.py +324 -0
  256. nautobot/data_validation/migrations/__init__.py +0 -0
  257. nautobot/data_validation/models.py +361 -0
  258. nautobot/data_validation/navigation.py +74 -0
  259. nautobot/data_validation/signals.py +30 -0
  260. nautobot/data_validation/tables.py +259 -0
  261. nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +1 -0
  262. nautobot/data_validation/templates/data_validation/datacompliance_tab.html +11 -0
  263. nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
  264. nautobot/data_validation/tests/__init__.py +20 -0
  265. nautobot/data_validation/tests/migrations/__init__.py +0 -0
  266. nautobot/data_validation/tests/migrations/test_migrations.py +489 -0
  267. nautobot/data_validation/tests/test_api.py +238 -0
  268. nautobot/data_validation/tests/test_custom_validators.py +423 -0
  269. nautobot/data_validation/tests/test_data_compliance_rules.py +85 -0
  270. nautobot/data_validation/tests/test_filters.py +240 -0
  271. nautobot/data_validation/tests/test_form_mixin.py +115 -0
  272. nautobot/data_validation/tests/test_models.py +393 -0
  273. nautobot/data_validation/tests/test_views.py +435 -0
  274. nautobot/data_validation/urls.py +21 -0
  275. nautobot/data_validation/views.py +227 -0
  276. nautobot/dcim/api/serializers.py +10 -13
  277. nautobot/dcim/api/urls.py +2 -0
  278. nautobot/dcim/api/views.py +7 -0
  279. nautobot/dcim/apps.py +4 -0
  280. nautobot/dcim/choices.py +16 -0
  281. nautobot/dcim/custom_validators.py +84 -0
  282. nautobot/dcim/filter_mixins.py +353 -4
  283. nautobot/dcim/{filters/__init__.py → filters.py} +70 -157
  284. nautobot/dcim/forms.py +12 -6
  285. nautobot/dcim/graphql/types.py +1 -0
  286. nautobot/dcim/migrations/0075_add_deviceclusterassignment.py +52 -0
  287. nautobot/dcim/migrations/0076_device_cluster_to_clusters_data_migration.py +40 -0
  288. nautobot/dcim/migrations/0077_remove_device_cluster.py +14 -0
  289. nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
  290. nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
  291. nautobot/dcim/models/__init__.py +2 -0
  292. nautobot/dcim/models/device_components.py +3 -1
  293. nautobot/dcim/models/devices.py +115 -51
  294. nautobot/dcim/navigation.py +7 -3
  295. nautobot/dcim/querysets.py +6 -0
  296. nautobot/dcim/signals.py +19 -0
  297. nautobot/dcim/tables/devices.py +9 -5
  298. nautobot/dcim/tables/template_code.py +191 -102
  299. nautobot/dcim/templates/dcim/cable.html +1 -1
  300. nautobot/dcim/templates/dcim/cable_connect.html +62 -146
  301. nautobot/dcim/templates/dcim/cable_retrieve.html +10 -10
  302. nautobot/dcim/templates/dcim/cable_trace.html +15 -17
  303. nautobot/dcim/templates/dcim/console_port_connection_list.html +2 -2
  304. nautobot/dcim/templates/dcim/consoleport.html +18 -17
  305. nautobot/dcim/templates/dcim/consoleserverport.html +18 -17
  306. nautobot/dcim/templates/dcim/controller_create.html +12 -8
  307. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +1 -1
  308. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +6 -6
  309. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -1
  310. nautobot/dcim/templates/dcim/device/config.html +17 -19
  311. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +4 -4
  312. nautobot/dcim/templates/dcim/device/status.html +20 -20
  313. nautobot/dcim/templates/dcim/device_component_add.html +24 -15
  314. nautobot/dcim/templates/dcim/device_create.html +120 -120
  315. nautobot/dcim/templates/dcim/device_list.html +75 -12
  316. nautobot/dcim/templates/dcim/devicebay.html +7 -7
  317. nautobot/dcim/templates/dcim/devicebay_populate.html +29 -23
  318. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +6 -6
  319. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  320. nautobot/dcim/templates/dcim/devicetype_component_add.html +25 -19
  321. nautobot/dcim/templates/dcim/devicetype_list.html +4 -4
  322. nautobot/dcim/templates/dcim/devicetype_update.html +9 -9
  323. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +3 -3
  324. nautobot/dcim/templates/dcim/frontport.html +21 -20
  325. nautobot/dcim/templates/dcim/inc/cable_form.html +7 -7
  326. nautobot/dcim/templates/dcim/inc/cable_termination.html +1 -1
  327. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +18 -9
  328. nautobot/dcim/templates/dcim/inc/detail_softwareversion_softwareimagefile_rows.html +1 -1
  329. nautobot/dcim/templates/dcim/inc/device_interface_filter.html +1 -1
  330. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +10 -10
  331. nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
  332. nautobot/dcim/templates/dcim/inc/homepage_connections.html +2 -2
  333. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +10 -10
  334. nautobot/dcim/templates/dcim/inc/rack_elevation.html +2 -2
  335. nautobot/dcim/templates/dcim/interface.html +42 -22
  336. nautobot/dcim/templates/dcim/interface_connection_list.html +2 -2
  337. nautobot/dcim/templates/dcim/interface_edit.html +26 -11
  338. nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +3 -3
  339. nautobot/dcim/templates/dcim/inventoryitem.html +3 -3
  340. nautobot/dcim/templates/dcim/inventoryitem_add.html +21 -10
  341. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  342. nautobot/dcim/templates/dcim/inventoryitem_edit.html +6 -4
  343. nautobot/dcim/templates/dcim/location.html +1 -1
  344. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +24 -18
  345. nautobot/dcim/templates/dcim/location_retrieve.html +1 -1
  346. nautobot/dcim/templates/dcim/location_update.html +9 -9
  347. nautobot/dcim/templates/dcim/locationtype.html +0 -1
  348. nautobot/dcim/templates/dcim/module/base.html +67 -27
  349. nautobot/dcim/templates/dcim/module_consoleports.html +13 -15
  350. nautobot/dcim/templates/dcim/module_consoleserverports.html +13 -15
  351. nautobot/dcim/templates/dcim/module_frontports.html +13 -15
  352. nautobot/dcim/templates/dcim/module_interfaces.html +14 -16
  353. nautobot/dcim/templates/dcim/module_list.html +59 -10
  354. nautobot/dcim/templates/dcim/module_modulebays.html +12 -14
  355. nautobot/dcim/templates/dcim/module_poweroutlets.html +13 -15
  356. nautobot/dcim/templates/dcim/module_powerports.html +13 -15
  357. nautobot/dcim/templates/dcim/module_rearports.html +13 -15
  358. nautobot/dcim/templates/dcim/module_retrieve.html +3 -3
  359. nautobot/dcim/templates/dcim/module_update.html +15 -9
  360. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -93
  361. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +7 -7
  362. nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
  363. nautobot/dcim/templates/dcim/moduletype_retrieve.html +74 -35
  364. nautobot/dcim/templates/dcim/platform_create.html +9 -9
  365. nautobot/dcim/templates/dcim/power_port_connection_list.html +3 -3
  366. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  367. nautobot/dcim/templates/dcim/powerfeed_edit.html +15 -15
  368. nautobot/dcim/templates/dcim/poweroutlet.html +13 -13
  369. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  370. nautobot/dcim/templates/dcim/powerport.html +17 -16
  371. nautobot/dcim/templates/dcim/rack.html +1 -1
  372. nautobot/dcim/templates/dcim/rack_elevation.html +2 -2
  373. nautobot/dcim/templates/dcim/rack_elevation_list.html +21 -9
  374. nautobot/dcim/templates/dcim/rack_retrieve.html +75 -57
  375. nautobot/dcim/templates/dcim/rack_update.html +14 -14
  376. nautobot/dcim/templates/dcim/rackreservation.html +1 -1
  377. nautobot/dcim/templates/dcim/rackreservation_edit.html +6 -6
  378. nautobot/dcim/templates/dcim/rearport.html +19 -18
  379. nautobot/dcim/templates/dcim/trace/cable.html +1 -1
  380. nautobot/dcim/templates/dcim/trace/circuit.html +1 -1
  381. nautobot/dcim/templates/dcim/trace/device.html +1 -1
  382. nautobot/dcim/templates/dcim/trace/powerpanel.html +1 -1
  383. nautobot/dcim/templates/dcim/trace/termination.html +1 -1
  384. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  385. nautobot/dcim/templates/dcim/virtualchassis_add_member.html +25 -16
  386. nautobot/dcim/templates/dcim/virtualchassis_create.html +6 -6
  387. nautobot/dcim/templates/dcim/virtualchassis_edit.html +1 -1
  388. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -1
  389. nautobot/dcim/templates/dcim/virtualchassis_update.html +36 -22
  390. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +9 -9
  391. nautobot/dcim/tests/integration/test_controller.py +6 -6
  392. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +7 -7
  393. nautobot/dcim/tests/integration/test_create_device.py +9 -9
  394. nautobot/dcim/tests/integration/test_device_bulk_operations.py +7 -2
  395. nautobot/dcim/tests/integration/test_fileinputpicker.py +5 -7
  396. nautobot/dcim/tests/integration/test_location_bulk_operations.py +2 -0
  397. nautobot/dcim/tests/integration/test_module_bay_position.py +4 -1
  398. nautobot/dcim/tests/test_api.py +86 -6
  399. nautobot/dcim/tests/test_custom_validators.py +229 -0
  400. nautobot/dcim/tests/test_filters.py +159 -110
  401. nautobot/dcim/tests/test_graphql.py +32 -36
  402. nautobot/dcim/tests/test_jobs.py +1 -1
  403. nautobot/dcim/tests/test_models.py +229 -1
  404. nautobot/dcim/tests/test_views.py +31 -20
  405. nautobot/dcim/utils.py +3 -3
  406. nautobot/dcim/views.py +77 -41
  407. nautobot/extras/api/serializers.py +83 -19
  408. nautobot/extras/api/urls.py +7 -0
  409. nautobot/extras/api/views.py +243 -140
  410. nautobot/extras/choices.py +34 -13
  411. nautobot/extras/constants.py +1 -1
  412. nautobot/extras/context_managers.py +26 -26
  413. nautobot/extras/datasources/git.py +22 -0
  414. nautobot/extras/datasources/registry.py +3 -0
  415. nautobot/extras/exceptions.py +5 -0
  416. nautobot/extras/factory.py +11 -1
  417. nautobot/extras/{filters/mixins.py → filter_mixins.py} +4 -3
  418. nautobot/extras/{filters/__init__.py → filters.py} +203 -58
  419. nautobot/extras/forms/base.py +2 -1
  420. nautobot/extras/forms/forms.py +225 -20
  421. nautobot/extras/forms/mixins.py +0 -41
  422. nautobot/extras/homepage.py +21 -2
  423. nautobot/extras/jobs.py +2 -8
  424. nautobot/extras/jobs_ui.py +2 -2
  425. nautobot/extras/management/__init__.py +9 -0
  426. nautobot/extras/managers.py +31 -22
  427. nautobot/extras/migrations/0126_approval_workflow_pre_check.py +58 -0
  428. nautobot/extras/migrations/0127_approval_workflow_models.py +266 -0
  429. nautobot/extras/migrations/0128_remove_job_approval_required_and_more.py +29 -0
  430. nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
  431. nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
  432. nautobot/extras/models/__init__.py +14 -3
  433. nautobot/extras/models/approvals.py +556 -0
  434. nautobot/extras/models/change_logging.py +1 -0
  435. nautobot/extras/models/contacts.py +2 -0
  436. nautobot/extras/models/customfields.py +57 -22
  437. nautobot/extras/models/datasources.py +21 -0
  438. nautobot/extras/models/groups.py +2 -0
  439. nautobot/extras/models/jobs.py +122 -39
  440. nautobot/extras/models/metadata.py +2 -3
  441. nautobot/extras/models/mixins.py +129 -1
  442. nautobot/extras/models/models.py +22 -14
  443. nautobot/extras/models/relationships.py +47 -10
  444. nautobot/extras/models/secrets.py +1 -0
  445. nautobot/extras/models/statuses.py +0 -15
  446. nautobot/extras/models/tags.py +1 -1
  447. nautobot/extras/navigation.py +42 -15
  448. nautobot/extras/plugins/__init__.py +33 -55
  449. nautobot/extras/plugins/marketplace_manifest.yml +1 -23
  450. nautobot/extras/plugins/tables.py +8 -6
  451. nautobot/extras/plugins/urls.py +2 -21
  452. nautobot/extras/plugins/utils.py +1 -33
  453. nautobot/extras/plugins/validators.py +10 -10
  454. nautobot/extras/plugins/views.py +1 -5
  455. nautobot/extras/querysets.py +17 -21
  456. nautobot/extras/signals.py +23 -8
  457. nautobot/extras/tables.py +420 -99
  458. nautobot/extras/templates/extras/approval_dashboard.html +15 -0
  459. nautobot/extras/templates/extras/approval_workflow/approve.html +11 -0
  460. nautobot/extras/templates/extras/approval_workflow/comment.html +9 -0
  461. nautobot/extras/templates/extras/approval_workflow/deny.html +10 -0
  462. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +77 -0
  463. nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +29 -0
  464. nautobot/extras/templates/extras/configcontext_update.html +12 -12
  465. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  466. nautobot/extras/templates/extras/configcontextschema_retrieve.html +9 -9
  467. nautobot/extras/templates/extras/configcontextschema_update.html +6 -6
  468. nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
  469. nautobot/extras/templates/extras/customfield_update.html +12 -12
  470. nautobot/extras/templates/extras/dynamicgroup.html +1 -1
  471. nautobot/extras/templates/extras/dynamicgroup_edit.html +1 -1
  472. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +17 -17
  473. nautobot/extras/templates/extras/dynamicgroup_update.html +24 -24
  474. nautobot/extras/templates/extras/externalintegration_update.html +6 -6
  475. nautobot/extras/templates/extras/gitrepository.html +1 -1
  476. nautobot/extras/templates/extras/gitrepository_object_edit.html +1 -1
  477. nautobot/extras/templates/extras/gitrepository_result.html +1 -1
  478. nautobot/extras/templates/extras/gitrepository_retrieve.html +12 -12
  479. nautobot/extras/templates/extras/gitrepository_update.html +25 -7
  480. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -1
  481. nautobot/extras/templates/extras/inc/approval_buttons_column.html +38 -0
  482. nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +14 -13
  483. nautobot/extras/templates/extras/inc/configcontext_format.html +11 -4
  484. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +7 -7
  485. nautobot/extras/templates/extras/inc/job_label.html +5 -5
  486. nautobot/extras/templates/extras/inc/job_table.html +23 -10
  487. nautobot/extras/templates/extras/inc/job_tiles.html +33 -21
  488. nautobot/extras/templates/extras/inc/jobresult.html +6 -6
  489. nautobot/extras/templates/extras/inc/json_format.html +11 -4
  490. nautobot/extras/templates/extras/inc/object_contact_header.html +6 -6
  491. nautobot/extras/templates/extras/inc/overridable_field.html +16 -15
  492. nautobot/extras/templates/extras/inc/panel_approvalworkflowstage.html +34 -0
  493. nautobot/extras/templates/extras/inc/panel_changelog.html +9 -9
  494. nautobot/extras/templates/extras/inc/panel_jobhistory.html +8 -6
  495. nautobot/extras/templates/extras/inc/tags_panel.html +3 -3
  496. nautobot/extras/templates/extras/job.html +154 -155
  497. nautobot/extras/templates/extras/job_approval_confirmation.html +4 -27
  498. nautobot/extras/templates/extras/job_bulk_edit.html +18 -1
  499. nautobot/extras/templates/extras/job_detail.html +1 -1
  500. nautobot/extras/templates/extras/job_edit.html +69 -64
  501. nautobot/extras/templates/extras/job_list.html +37 -60
  502. nautobot/extras/templates/extras/jobresult.html +1 -1
  503. nautobot/extras/templates/extras/jobresult_retrieve.html +17 -17
  504. nautobot/extras/templates/extras/marketplace.html +62 -71
  505. nautobot/extras/templates/extras/metadatatype_create.html +9 -9
  506. nautobot/extras/templates/extras/note.html +1 -1
  507. nautobot/extras/templates/extras/object_approvalworkflow.html +36 -0
  508. nautobot/extras/templates/extras/object_assign_contact_or_team.html +16 -7
  509. nautobot/extras/templates/extras/object_configcontext.html +20 -20
  510. nautobot/extras/templates/extras/object_new_contact.html +6 -6
  511. nautobot/extras/templates/extras/object_new_team.html +6 -6
  512. nautobot/extras/templates/extras/objectchange.html +1 -1
  513. nautobot/extras/templates/extras/objectchange_retrieve.html +37 -56
  514. nautobot/extras/templates/extras/plugin_detail.html +40 -41
  515. nautobot/extras/templates/extras/plugins_list.html +23 -38
  516. nautobot/extras/templates/extras/plugins_tiles.html +28 -28
  517. nautobot/extras/templates/extras/role_retrieve.html +112 -48
  518. nautobot/extras/templates/extras/scheduledjob.html +25 -28
  519. nautobot/extras/templates/extras/secret_create.html +11 -11
  520. nautobot/extras/templates/extras/secretsgroup_update.html +6 -6
  521. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +3 -3
  522. nautobot/extras/templates/extras/status.html +1 -1
  523. nautobot/extras/templates/extras/tag.html +1 -1
  524. nautobot/extras/templates/extras/tag_update.html +3 -3
  525. nautobot/extras/templates/extras/templatetags/log_level.html +1 -1
  526. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -2
  527. nautobot/extras/templates/extras/webhook.html +12 -12
  528. nautobot/extras/templatetags/approvals.py +19 -0
  529. nautobot/extras/templatetags/custom_links.py +12 -12
  530. nautobot/extras/templatetags/job_buttons.py +14 -12
  531. nautobot/extras/test_jobs/invalid_import.py +9 -0
  532. nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
  533. nautobot/extras/test_jobs/missing_import.py +11 -0
  534. nautobot/extras/tests/integration/test_computedfields.py +5 -8
  535. nautobot/extras/tests/integration/test_configcontextschema.py +43 -48
  536. nautobot/extras/tests/integration/test_customfields.py +33 -33
  537. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -10
  538. nautobot/extras/tests/integration/test_jobs.py +2 -4
  539. nautobot/extras/tests/integration/test_notes.py +3 -9
  540. nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
  541. nautobot/extras/tests/integration/test_plugins.py +35 -27
  542. nautobot/extras/tests/integration/test_relationships.py +7 -11
  543. nautobot/extras/tests/integration/test_tagfilter.py +3 -11
  544. nautobot/extras/tests/test_api.py +786 -242
  545. nautobot/extras/tests/test_approvals.py +715 -0
  546. nautobot/extras/tests/test_changelog.py +18 -14
  547. nautobot/extras/tests/test_customfields.py +14 -13
  548. nautobot/extras/tests/test_datasources.py +1 -1
  549. nautobot/extras/tests/test_dynamicgroups.py +9 -4
  550. nautobot/extras/tests/test_filters.py +443 -13
  551. nautobot/extras/tests/test_forms.py +18 -57
  552. nautobot/extras/tests/test_jobs.py +25 -4
  553. nautobot/extras/tests/test_migrations.py +81 -1
  554. nautobot/extras/tests/test_models.py +378 -47
  555. nautobot/extras/tests/test_plugins.py +47 -13
  556. nautobot/extras/tests/test_relationships.py +7 -2
  557. nautobot/extras/tests/test_utils.py +2 -0
  558. nautobot/extras/tests/test_views.py +780 -493
  559. nautobot/extras/urls.py +36 -12
  560. nautobot/extras/utils.py +58 -12
  561. nautobot/extras/views.py +668 -209
  562. nautobot/ipam/factory.py +7 -0
  563. nautobot/ipam/filter_mixins.py +38 -0
  564. nautobot/ipam/filters.py +35 -71
  565. nautobot/ipam/formfields.py +1 -1
  566. nautobot/ipam/forms.py +6 -3
  567. nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
  568. nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
  569. nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
  570. nautobot/ipam/models.py +29 -2
  571. nautobot/ipam/navigation.py +3 -1
  572. nautobot/ipam/querysets.py +1 -2
  573. nautobot/ipam/tables.py +26 -17
  574. nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +6 -6
  575. nautobot/ipam/templates/ipam/inc/service.html +8 -8
  576. nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
  577. nautobot/ipam/templates/ipam/inc/vlangroup_header.html +3 -2
  578. nautobot/ipam/templates/ipam/ipaddress.html +27 -13
  579. nautobot/ipam/templates/ipam/ipaddress_assign.html +31 -24
  580. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +3 -3
  581. nautobot/ipam/templates/ipam/ipaddress_edit.html +9 -9
  582. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +7 -9
  583. nautobot/ipam/templates/ipam/ipaddress_merge.html +195 -186
  584. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +7 -9
  585. nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +7 -5
  586. nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
  587. nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
  588. nautobot/ipam/templates/ipam/namespace_update.html +15 -0
  589. nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
  590. nautobot/ipam/templates/ipam/prefix_create.html +9 -9
  591. nautobot/ipam/templates/ipam/prefix_list.html +15 -14
  592. nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -1
  593. nautobot/ipam/templates/ipam/vlan.html +1 -1
  594. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  595. nautobot/ipam/templates/ipam/vlan_update.html +6 -6
  596. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  597. nautobot/ipam/templates/ipam/vrf_edit.html +15 -15
  598. nautobot/ipam/tests/integration/test_prefixes.py +5 -13
  599. nautobot/ipam/tests/migration/test_migrations.py +89 -0
  600. nautobot/ipam/tests/test_api.py +20 -7
  601. nautobot/ipam/tests/test_filters.py +10 -0
  602. nautobot/ipam/tests/test_forms.py +1 -1
  603. nautobot/ipam/tests/test_models.py +1 -1
  604. nautobot/ipam/tests/test_tables.py +1 -2
  605. nautobot/ipam/tests/test_utils.py +1 -1
  606. nautobot/ipam/tests/test_views.py +24 -21
  607. nautobot/ipam/ui.py +0 -17
  608. nautobot/ipam/utils/migrations.py +16 -2
  609. nautobot/ipam/utils/testing.py +9 -3
  610. nautobot/ipam/views.py +49 -7
  611. nautobot/project-static/dist/css/graphql-libraries.css +655 -0
  612. nautobot/project-static/dist/css/graphql-libraries.css.map +1 -0
  613. nautobot/project-static/dist/css/materialdesignicons.css +3 -0
  614. nautobot/project-static/dist/css/materialdesignicons.css.map +1 -0
  615. nautobot/project-static/dist/css/nautobot.css +13 -0
  616. nautobot/project-static/dist/css/nautobot.css.map +1 -0
  617. nautobot/project-static/dist/js/graphql-libraries.js +3 -0
  618. nautobot/project-static/dist/js/graphql-libraries.js.LICENSE.txt +62 -0
  619. nautobot/project-static/dist/js/graphql-libraries.js.map +1 -0
  620. nautobot/project-static/dist/js/libraries.js +3 -0
  621. nautobot/project-static/dist/js/libraries.js.LICENSE.txt +65 -0
  622. nautobot/project-static/dist/js/libraries.js.map +1 -0
  623. nautobot/project-static/dist/js/materialdesignicons.js +0 -0
  624. nautobot/project-static/dist/js/nautobot-graphiql.js +2 -0
  625. nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -0
  626. nautobot/project-static/dist/js/nautobot.js +2 -0
  627. nautobot/project-static/dist/js/nautobot.js.map +1 -0
  628. nautobot/project-static/fonts/Montserrat-v30-Bold.woff2 +0 -0
  629. nautobot/project-static/fonts/Montserrat-v30-Light.woff2 +0 -0
  630. nautobot/project-static/fonts/Montserrat-v30-Regular.woff2 +0 -0
  631. nautobot/project-static/fonts/Roboto-v48-Bold.woff2 +0 -0
  632. nautobot/project-static/fonts/Roboto-v48-Light.woff2 +0 -0
  633. nautobot/project-static/fonts/Roboto-v48-Regular.woff2 +0 -0
  634. nautobot/project-static/img/jinja_logo.svg +21 -92
  635. nautobot/project-static/js/cabletrace.js +1 -1
  636. nautobot/project-static/js/editor.js +4 -4
  637. nautobot/project-static/js/forms.js +67 -717
  638. nautobot/project-static/js/job_result.js +2 -2
  639. nautobot/project-static/nautobot-icons/360-degrees.svg +3 -0
  640. nautobot/project-static/nautobot-icons/arrow-decision.svg +3 -0
  641. nautobot/project-static/nautobot-icons/arrows-expand-rec.svg +3 -0
  642. nautobot/project-static/nautobot-icons/arrows-move-2-rec.svg +3 -0
  643. nautobot/project-static/nautobot-icons/arrows-move-rec.svg +3 -0
  644. nautobot/project-static/nautobot-icons/atom.svg +3 -0
  645. nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
  646. nautobot/project-static/nautobot-icons/branch.svg +3 -0
  647. nautobot/project-static/nautobot-icons/briefcase-2.svg +3 -0
  648. nautobot/project-static/nautobot-icons/cable-data-2.svg +3 -0
  649. nautobot/project-static/nautobot-icons/cable-data.svg +3 -0
  650. nautobot/project-static/nautobot-icons/cast.svg +3 -0
  651. nautobot/project-static/nautobot-icons/check-circle.svg +3 -0
  652. nautobot/project-static/nautobot-icons/checkbox-circle.svg +3 -0
  653. nautobot/project-static/nautobot-icons/checkbox-rec.svg +3 -0
  654. nautobot/project-static/nautobot-icons/cloud-check.svg +3 -0
  655. nautobot/project-static/nautobot-icons/cloud-lightning.svg +3 -0
  656. nautobot/project-static/nautobot-icons/cloud-upload.svg +3 -0
  657. nautobot/project-static/nautobot-icons/cloud.svg +3 -0
  658. nautobot/project-static/nautobot-icons/compass.svg +3 -0
  659. nautobot/project-static/nautobot-icons/control-panel.svg +3 -0
  660. nautobot/project-static/nautobot-icons/credit-card.svg +3 -0
  661. nautobot/project-static/nautobot-icons/device-lifecycle.svg +3 -0
  662. nautobot/project-static/nautobot-icons/direction.svg +3 -0
  663. nautobot/project-static/nautobot-icons/elements.svg +3 -0
  664. nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
  665. nautobot/project-static/nautobot-icons/globe-2.svg +3 -0
  666. nautobot/project-static/nautobot-icons/globe.svg +3 -0
  667. nautobot/project-static/nautobot-icons/hammer.svg +3 -0
  668. nautobot/project-static/nautobot-icons/history.svg +3 -0
  669. nautobot/project-static/nautobot-icons/ip.svg +3 -0
  670. nautobot/project-static/nautobot-icons/laptop.svg +3 -0
  671. nautobot/project-static/nautobot-icons/lightning.svg +3 -0
  672. nautobot/project-static/nautobot-icons/list-unordered.svg +3 -0
  673. nautobot/project-static/nautobot-icons/map-view.svg +3 -0
  674. nautobot/project-static/nautobot-icons/organization.svg +3 -0
  675. nautobot/project-static/nautobot-icons/pin-2.svg +3 -0
  676. nautobot/project-static/nautobot-icons/pin-3.svg +3 -0
  677. nautobot/project-static/nautobot-icons/plug.svg +3 -0
  678. nautobot/project-static/nautobot-icons/refresh-cw.svg +3 -0
  679. nautobot/project-static/nautobot-icons/rocket-2.svg +3 -0
  680. nautobot/project-static/nautobot-icons/rotate-cw.svg +3 -0
  681. nautobot/project-static/nautobot-icons/route.svg +3 -0
  682. nautobot/project-static/nautobot-icons/secrets.svg +3 -0
  683. nautobot/project-static/nautobot-icons/security.svg +3 -0
  684. nautobot/project-static/nautobot-icons/server-2.svg +3 -0
  685. nautobot/project-static/nautobot-icons/server.svg +3 -0
  686. nautobot/project-static/nautobot-icons/share.svg +3 -0
  687. nautobot/project-static/nautobot-icons/shield-check.svg +3 -0
  688. nautobot/project-static/nautobot-icons/sitemap-outline.svg +3 -0
  689. nautobot/project-static/nautobot-icons/sliders-vert-2.svg +3 -0
  690. nautobot/project-static/nautobot-icons/sliders-vert.svg +3 -0
  691. nautobot/project-static/nautobot-icons/star-filled.svg +3 -0
  692. nautobot/project-static/nautobot-icons/star.svg +3 -0
  693. nautobot/project-static/nautobot-icons/transform.svg +3 -0
  694. nautobot/project-static/nautobot-icons/wifi.svg +3 -0
  695. nautobot/tenancy/api/serializers.py +1 -0
  696. nautobot/tenancy/api/views.py +2 -1
  697. nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
  698. nautobot/tenancy/navigation.py +3 -1
  699. nautobot/tenancy/templates/tenancy/tenant_create.html +6 -6
  700. nautobot/tenancy/tests/test_filters.py +0 -2
  701. nautobot/tenancy/views.py +2 -1
  702. nautobot/ui/.gitignore +137 -0
  703. nautobot/ui/.node-version +1 -0
  704. nautobot/ui/.prettierignore +3 -0
  705. nautobot/ui/eslint.config.js +33 -0
  706. nautobot/ui/package-lock.json +6594 -0
  707. nautobot/ui/package.json +67 -0
  708. nautobot/ui/prettier.config.js +9 -0
  709. nautobot/ui/src/js/collapse.js +69 -0
  710. nautobot/ui/src/js/cookie.js +31 -0
  711. nautobot/ui/src/js/draggable.js +101 -0
  712. nautobot/ui/src/js/drawer.js +106 -0
  713. nautobot/ui/src/js/form.js +23 -0
  714. nautobot/ui/src/js/history.js +51 -0
  715. nautobot/ui/src/js/nautobot-graphiql.js +19 -0
  716. nautobot/ui/src/js/nautobot.js +128 -0
  717. nautobot/ui/src/js/search.js +274 -0
  718. nautobot/ui/src/js/select2.js +318 -0
  719. nautobot/ui/src/js/sidenav.js +87 -0
  720. nautobot/ui/src/js/tabs.js +139 -0
  721. nautobot/ui/src/js/theme.js +104 -0
  722. nautobot/ui/src/js/utils.js +54 -0
  723. nautobot/ui/src/scss/colors.scss +58 -0
  724. nautobot/ui/src/scss/nautobot.scss +2471 -0
  725. nautobot/ui/webpack.config.js +148 -0
  726. nautobot/users/apps.py +3 -0
  727. nautobot/users/filters.py +7 -11
  728. nautobot/users/forms.py +10 -0
  729. nautobot/users/models.py +8 -0
  730. nautobot/users/templates/users/advanced_settings_edit.html +31 -21
  731. nautobot/users/templates/users/api_tokens.html +61 -51
  732. nautobot/users/templates/users/base.html +23 -31
  733. nautobot/users/templates/users/change_password.html +29 -19
  734. nautobot/users/templates/users/preferences.html +55 -45
  735. nautobot/users/templates/users/profile.html +45 -14
  736. nautobot/users/tests/test_api.py +4 -0
  737. nautobot/users/urls.py +2 -0
  738. nautobot/users/views.py +70 -2
  739. nautobot/virtualization/api/views.py +1 -1
  740. nautobot/virtualization/filters.py +18 -32
  741. nautobot/virtualization/forms.py +22 -59
  742. nautobot/virtualization/models.py +1 -19
  743. nautobot/virtualization/navigation.py +3 -1
  744. nautobot/virtualization/tables.py +10 -6
  745. nautobot/virtualization/templates/virtualization/cluster.html +13 -13
  746. nautobot/virtualization/templates/virtualization/cluster_edit.html +6 -6
  747. nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +1 -1
  748. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  749. nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +24 -16
  750. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +1 -1
  751. nautobot/virtualization/templates/virtualization/virtualmachine_list.html +4 -4
  752. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +27 -25
  753. nautobot/virtualization/templates/virtualization/vminterface.html +5 -5
  754. nautobot/virtualization/templates/virtualization/vminterface_edit.html +27 -11
  755. nautobot/virtualization/tests/test_api.py +3 -0
  756. nautobot/virtualization/tests/test_models.py +20 -5
  757. nautobot/virtualization/tests/test_views.py +3 -5
  758. nautobot/virtualization/urls.py +0 -11
  759. nautobot/virtualization/views.py +5 -122
  760. nautobot/vpn/__init__.py +0 -0
  761. nautobot/vpn/api/serializers.py +113 -0
  762. nautobot/vpn/api/urls.py +19 -0
  763. nautobot/vpn/api/views.py +70 -0
  764. nautobot/vpn/apps.py +8 -0
  765. nautobot/vpn/choices.py +171 -0
  766. nautobot/vpn/factory.py +209 -0
  767. nautobot/vpn/filters.py +233 -0
  768. nautobot/vpn/forms.py +486 -0
  769. nautobot/vpn/homepage.py +19 -0
  770. nautobot/vpn/migrations/0001_initial.py +541 -0
  771. nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
  772. nautobot/vpn/migrations/__init__.py +0 -0
  773. nautobot/vpn/models.py +527 -0
  774. nautobot/vpn/navigation.py +98 -0
  775. nautobot/vpn/tables.py +380 -0
  776. nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
  777. nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
  778. nautobot/vpn/tests/__init__.py +0 -0
  779. nautobot/vpn/tests/test_api.py +341 -0
  780. nautobot/vpn/tests/test_filters.py +139 -0
  781. nautobot/vpn/tests/test_forms.py +294 -0
  782. nautobot/vpn/tests/test_models.py +97 -0
  783. nautobot/vpn/tests/test_views.py +281 -0
  784. nautobot/vpn/urls.py +16 -0
  785. nautobot/vpn/views.py +437 -0
  786. nautobot/wireless/filters.py +0 -8
  787. nautobot/wireless/navigation.py +3 -1
  788. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +6 -6
  789. nautobot/wireless/tests/integration/test_radio_profile.py +3 -7
  790. nautobot/wireless/tests/test_api.py +1 -1
  791. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +5 -4
  792. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +802 -707
  793. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
  794. nautobot/core/management/commands/check_job_approval_status.py +0 -47
  795. nautobot/core/templates/search_form.html +0 -9
  796. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +0 -87
  797. nautobot/dcim/filters/mixins.py +0 -354
  798. nautobot/extras/templates/extras/job_approval_request.html +0 -134
  799. nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +0 -28
  800. nautobot/ipam/mixins.py +0 -32
  801. nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
  802. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +0 -587
  803. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +0 -1
  804. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +0 -6
  805. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +0 -1
  806. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +0 -6865
  807. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +0 -1
  808. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +0 -6
  809. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +0 -1
  810. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
  811. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +0 -288
  812. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  813. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
  814. nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  815. nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.js +0 -2580
  816. nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js +0 -6
  817. nautobot/project-static/bootstrap-3.4.1-dist/js/npm.js +0 -13
  818. nautobot/project-static/clipboard.js-2.0.9/clipboard.min.js +0 -7
  819. nautobot/project-static/css/base.css +0 -1040
  820. nautobot/project-static/css/dark.css +0 -282
  821. nautobot/project-static/flatpickr-4.6.9/flatpickr.min.js +0 -2
  822. nautobot/project-static/flatpickr-4.6.9/themes/light.min.css +0 -1
  823. nautobot/project-static/graphiql-1.5.16/graphiql.min.css +0 -12
  824. nautobot/project-static/graphiql-1.5.16/graphiql.min.js +0 -11
  825. nautobot/project-static/highlight.js-11.9.0/github-dark.min.css +0 -10
  826. nautobot/project-static/highlight.js-11.9.0/github.min.css +0 -10
  827. nautobot/project-static/highlight.js-11.9.0/highlight.min.js +0 -378
  828. nautobot/project-static/jquery/jquery-3.7.1.min.js +0 -2
  829. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_444444_256x240.png +0 -0
  830. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_555555_256x240.png +0 -0
  831. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777620_256x240.png +0 -0
  832. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777777_256x240.png +0 -0
  833. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_cc0000_256x240.png +0 -0
  834. nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_ffffff_256x240.png +0 -0
  835. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +0 -7
  836. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +0 -6
  837. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +0 -5
  838. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.theme.min.css +0 -5
  839. nautobot/project-static/js/homepage_layout.js +0 -182
  840. nautobot/project-static/js/nav_menu.js +0 -250
  841. nautobot/project-static/js/theme.js +0 -133
  842. nautobot/project-static/materialdesignicons-7.4.47/LICENSE +0 -20
  843. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +0 -3
  844. nautobot/project-static/react-16.14.0/react.production.min.js +0 -32
  845. nautobot/project-static/react-dom-16.14.0/react-dom.production.min.js +0 -239
  846. nautobot/project-static/select2-4.0.13/i18n/af.js +0 -3
  847. nautobot/project-static/select2-4.0.13/i18n/ar.js +0 -3
  848. nautobot/project-static/select2-4.0.13/i18n/az.js +0 -3
  849. nautobot/project-static/select2-4.0.13/i18n/bg.js +0 -3
  850. nautobot/project-static/select2-4.0.13/i18n/bn.js +0 -3
  851. nautobot/project-static/select2-4.0.13/i18n/bs.js +0 -3
  852. nautobot/project-static/select2-4.0.13/i18n/ca.js +0 -3
  853. nautobot/project-static/select2-4.0.13/i18n/cs.js +0 -3
  854. nautobot/project-static/select2-4.0.13/i18n/da.js +0 -3
  855. nautobot/project-static/select2-4.0.13/i18n/de.js +0 -3
  856. nautobot/project-static/select2-4.0.13/i18n/dsb.js +0 -3
  857. nautobot/project-static/select2-4.0.13/i18n/el.js +0 -3
  858. nautobot/project-static/select2-4.0.13/i18n/en.js +0 -3
  859. nautobot/project-static/select2-4.0.13/i18n/es.js +0 -3
  860. nautobot/project-static/select2-4.0.13/i18n/et.js +0 -3
  861. nautobot/project-static/select2-4.0.13/i18n/eu.js +0 -3
  862. nautobot/project-static/select2-4.0.13/i18n/fa.js +0 -3
  863. nautobot/project-static/select2-4.0.13/i18n/fi.js +0 -3
  864. nautobot/project-static/select2-4.0.13/i18n/fr.js +0 -3
  865. nautobot/project-static/select2-4.0.13/i18n/gl.js +0 -3
  866. nautobot/project-static/select2-4.0.13/i18n/he.js +0 -3
  867. nautobot/project-static/select2-4.0.13/i18n/hi.js +0 -3
  868. nautobot/project-static/select2-4.0.13/i18n/hr.js +0 -3
  869. nautobot/project-static/select2-4.0.13/i18n/hsb.js +0 -3
  870. nautobot/project-static/select2-4.0.13/i18n/hu.js +0 -3
  871. nautobot/project-static/select2-4.0.13/i18n/hy.js +0 -3
  872. nautobot/project-static/select2-4.0.13/i18n/id.js +0 -3
  873. nautobot/project-static/select2-4.0.13/i18n/is.js +0 -3
  874. nautobot/project-static/select2-4.0.13/i18n/it.js +0 -3
  875. nautobot/project-static/select2-4.0.13/i18n/ja.js +0 -3
  876. nautobot/project-static/select2-4.0.13/i18n/ka.js +0 -3
  877. nautobot/project-static/select2-4.0.13/i18n/km.js +0 -3
  878. nautobot/project-static/select2-4.0.13/i18n/ko.js +0 -3
  879. nautobot/project-static/select2-4.0.13/i18n/lt.js +0 -3
  880. nautobot/project-static/select2-4.0.13/i18n/lv.js +0 -3
  881. nautobot/project-static/select2-4.0.13/i18n/mk.js +0 -3
  882. nautobot/project-static/select2-4.0.13/i18n/ms.js +0 -3
  883. nautobot/project-static/select2-4.0.13/i18n/nb.js +0 -3
  884. nautobot/project-static/select2-4.0.13/i18n/ne.js +0 -3
  885. nautobot/project-static/select2-4.0.13/i18n/nl.js +0 -3
  886. nautobot/project-static/select2-4.0.13/i18n/pl.js +0 -3
  887. nautobot/project-static/select2-4.0.13/i18n/ps.js +0 -3
  888. nautobot/project-static/select2-4.0.13/i18n/pt-BR.js +0 -3
  889. nautobot/project-static/select2-4.0.13/i18n/pt.js +0 -3
  890. nautobot/project-static/select2-4.0.13/i18n/ro.js +0 -3
  891. nautobot/project-static/select2-4.0.13/i18n/ru.js +0 -3
  892. nautobot/project-static/select2-4.0.13/i18n/sk.js +0 -3
  893. nautobot/project-static/select2-4.0.13/i18n/sl.js +0 -3
  894. nautobot/project-static/select2-4.0.13/i18n/sq.js +0 -3
  895. nautobot/project-static/select2-4.0.13/i18n/sr-Cyrl.js +0 -3
  896. nautobot/project-static/select2-4.0.13/i18n/sr.js +0 -3
  897. nautobot/project-static/select2-4.0.13/i18n/sv.js +0 -3
  898. nautobot/project-static/select2-4.0.13/i18n/th.js +0 -3
  899. nautobot/project-static/select2-4.0.13/i18n/tk.js +0 -3
  900. nautobot/project-static/select2-4.0.13/i18n/tr.js +0 -3
  901. nautobot/project-static/select2-4.0.13/i18n/uk.js +0 -3
  902. nautobot/project-static/select2-4.0.13/i18n/vi.js +0 -3
  903. nautobot/project-static/select2-4.0.13/i18n/zh-CN.js +0 -3
  904. nautobot/project-static/select2-4.0.13/i18n/zh-TW.js +0 -3
  905. nautobot/project-static/select2-4.0.13/select2.min.css +0 -1
  906. nautobot/project-static/select2-4.0.13/select2.min.js +0 -2
  907. nautobot/project-static/select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css +0 -7
  908. nautobot/project-static/subscriptions-transport-ws-0.9.18/client.min.js +0 -8
  909. nautobot/project-static/whatwg-fetch-3.6.2/fetch.umd.min.js +0 -8
  910. nautobot/virtualization/templates/virtualization/cluster_add_devices.html +0 -37
  911. /nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +0 -0
  912. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.ttf → dist/1fcc36272ea3e53d0031.ttf} +0 -0
  913. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.eot → dist/2146c3c82b553977abc7.eot} +0 -0
  914. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff → dist/e55a20c80650829ec5fd.woff} +0 -0
  915. /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 → dist/ec024da790d2972da002.woff2} +0 -0
  916. /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
  917. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
  918. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
  919. {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
@@ -36,6 +36,7 @@ from nautobot.core.models.querysets import RestrictedQuerySet
36
36
  from nautobot.core.models.validators import validate_regex
37
37
  from nautobot.core.settings_funcs import is_truthy
38
38
  from nautobot.core.templatetags.helpers import render_markdown
39
+ from nautobot.core.utils.cache import construct_cache_key
39
40
  from nautobot.core.utils.data import render_jinja2, validate_jinja2
40
41
  from nautobot.extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
41
42
  from nautobot.extras.models import ChangeLoggedModel
@@ -56,8 +57,12 @@ class ComputedFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
56
57
  Returns a queryset by default, or a list if `get_queryset` param is False.
57
58
  """
58
59
  concrete_model = model._meta.concrete_model
59
- cache_key = f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
60
- list_cache_key = f"{cache_key}.list"
60
+ cache_key = construct_cache_key(
61
+ self, method_name="get_for_model", branch_aware=True, model=concrete_model._meta.label_lower
62
+ )
63
+ list_cache_key = construct_cache_key(
64
+ self, method_name="get_for_model", branch_aware=True, model=concrete_model._meta.label_lower, listing=True
65
+ )
61
66
  if not get_queryset:
62
67
  listing = cache.get(list_cache_key)
63
68
  if listing is not None:
@@ -73,8 +78,6 @@ class ComputedFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
73
78
  return listing
74
79
  return queryset
75
80
 
76
- get_for_model.cache_key_prefix = "nautobot.extras.computedfield.get_for_model"
77
-
78
81
  def populate_list_caches(self):
79
82
  """Populate all caches for `get_for_model(..., get_queryset=False)` lookups."""
80
83
  queryset = self.all().select_related("content_type")
@@ -83,7 +86,10 @@ class ComputedFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
83
86
  listings[f"{cf.content_type.app_label}.{cf.content_type.model}"].append(cf)
84
87
  for ct in ContentType.objects.all():
85
88
  label = f"{ct.app_label}.{ct.model}"
86
- cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.list", listings[label])
89
+ cache_key = construct_cache_key(
90
+ self, method_name="get_for_model", branch_aware=True, model=label, listing=True
91
+ )
92
+ cache.set(cache_key, listings[label])
87
93
 
88
94
  def bulk_create(self, objs, *args, **kwargs):
89
95
  """Validate templates before saving."""
@@ -437,10 +443,21 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
437
443
  get_queryset (bool): Whether to return a QuerySet or a list.
438
444
  """
439
445
  concrete_model = model._meta.concrete_model
440
- cache_key = (
441
- f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}.{exclude_filter_disabled}"
446
+ cache_key = construct_cache_key(
447
+ self,
448
+ method_name="get_for_model",
449
+ branch_aware=True,
450
+ model=concrete_model._meta.label_lower,
451
+ exclude_filter_disabled=exclude_filter_disabled,
452
+ )
453
+ list_cache_key = construct_cache_key(
454
+ self,
455
+ method_name="get_for_model",
456
+ branch_aware=True,
457
+ model=concrete_model._meta.label_lower,
458
+ exclude_filter_disabled=exclude_filter_disabled,
459
+ listing=True,
442
460
  )
443
- list_cache_key = f"{cache_key}.list"
444
461
  if not get_queryset:
445
462
  listing = cache.get(list_cache_key)
446
463
  if listing is not None:
@@ -458,20 +475,18 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
458
475
  return listing
459
476
  return queryset
460
477
 
461
- get_for_model.cache_key_prefix = "nautobot.extras.customfield.get_for_model"
462
-
463
478
  def keys_for_model(self, model):
464
479
  """Return list of all keys for CustomFields assigned to the given model."""
465
480
  concrete_model = model._meta.concrete_model
466
- cache_key = f"{self.keys_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
481
+ cache_key = construct_cache_key(
482
+ self, method_name="keys_for_model", branch_aware=True, model=concrete_model._meta.label_lower
483
+ )
467
484
  keys = cache.get(cache_key)
468
485
  if keys is None:
469
486
  keys = list(self.get_for_model(model).values_list("key", flat=True))
470
487
  cache.set(cache_key, keys)
471
488
  return keys
472
489
 
473
- keys_for_model.cache_key_prefix = "nautobot.extras.customfield.keys_for_model"
474
-
475
490
  def populate_list_caches(self):
476
491
  """Populate all caches for `get_for_model(..., get_queryset=False)` and `keys_for_model` lookups."""
477
492
  queryset = self.all().prefetch_related("content_types")
@@ -486,9 +501,32 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
486
501
  key_listings[label].append(cf.key)
487
502
  for ct in ContentType.objects.all():
488
503
  label = f"{ct.app_label}.{ct.model}"
489
- cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.True.list", cf_listings[label][True])
490
- cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.False.list", cf_listings[label][False])
491
- cache.set(f"{self.keys_for_model.cache_key_prefix}.{label}", key_listings[label])
504
+ cache.set(
505
+ construct_cache_key(
506
+ self,
507
+ method_name="get_for_model",
508
+ branch_aware=True,
509
+ model=label,
510
+ exclude_filter_disabled=True,
511
+ listing=True,
512
+ ),
513
+ cf_listings[label][True],
514
+ )
515
+ cache.set(
516
+ construct_cache_key(
517
+ self,
518
+ method_name="get_for_model",
519
+ branch_aware=True,
520
+ model=label,
521
+ exclude_filter_disabled=True,
522
+ listing=False,
523
+ ),
524
+ cf_listings[label][False],
525
+ )
526
+ cache.set(
527
+ construct_cache_key(self, method_name="keys_for_model", branch_aware=True, model=label),
528
+ key_listings[label],
529
+ )
492
530
 
493
531
 
494
532
  @extras_features("webhooks")
@@ -607,10 +645,6 @@ class CustomField(
607
645
  def __str__(self):
608
646
  return self.label
609
647
 
610
- @property
611
- def choices_cache_key(self):
612
- return f"nautobot.extras.customfield.choices.{self.pk}"
613
-
614
648
  @property
615
649
  def choices(self) -> list[str]:
616
650
  """
@@ -621,10 +655,11 @@ class CustomField(
621
655
  """
622
656
  if self.type not in [CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT]:
623
657
  return []
624
- choices = cache.get(self.choices_cache_key)
658
+ cache_key = construct_cache_key(self, method_name="choices", branch_aware=True)
659
+ choices = cache.get(cache_key)
625
660
  if choices is None:
626
661
  choices = list(self.custom_field_choices.order_by("weight", "value").values_list("value", flat=True))
627
- cache.set(self.choices_cache_key, choices)
662
+ cache.set(cache_key, choices)
628
663
  return choices
629
664
 
630
665
  def save(self, *args, **kwargs):
@@ -7,14 +7,18 @@ import shutil
7
7
  import tempfile
8
8
 
9
9
  from django.conf import settings
10
+ from django.core.cache import cache
10
11
  from django.core.exceptions import ValidationError
11
12
  from django.core.serializers.json import DjangoJSONEncoder
12
13
  from django.db import models
13
14
 
14
15
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
16
+ from nautobot.core.models import BaseManager
15
17
  from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
16
18
  from nautobot.core.models.generics import PrimaryModel
19
+ from nautobot.core.models.querysets import RestrictedQuerySet
17
20
  from nautobot.core.models.validators import EnhancedURLValidator
21
+ from nautobot.core.utils.cache import construct_cache_key
18
22
  from nautobot.core.utils.git import GitRepo
19
23
  from nautobot.core.utils.module_loading import check_name_safe_to_import_privately
20
24
  from nautobot.extras.utils import extras_features
@@ -22,6 +26,21 @@ from nautobot.extras.utils import extras_features
22
26
  logger = logging.getLogger(__name__)
23
27
 
24
28
 
29
+ class GitRepositoryManager(BaseManager.from_queryset(RestrictedQuerySet)):
30
+ def get_for_provided_contents(self, provided_contents_type):
31
+ cache_key = construct_cache_key(
32
+ self,
33
+ method_name="get_for_provided_contents",
34
+ branch_aware=True,
35
+ provided_contents_type=provided_contents_type,
36
+ )
37
+ queryset = cache.get(cache_key)
38
+ if queryset is None:
39
+ queryset = self.get_queryset().filter(provided_contents__contains=provided_contents_type)
40
+ cache.set(cache_key, queryset)
41
+ return queryset
42
+
43
+
25
44
  @extras_features(
26
45
  "config_context_owners",
27
46
  "export_template_owners",
@@ -75,6 +94,8 @@ class GitRepository(PrimaryModel):
75
94
  # the data types registered in registry['datasource_contents'].
76
95
  provided_contents = models.JSONField(encoder=DjangoJSONEncoder, default=list, blank=True)
77
96
 
97
+ objects = GitRepositoryManager()
98
+
78
99
  clone_fields = ["remote_url", "secrets_group", "provided_contents"]
79
100
 
80
101
  class Meta:
@@ -76,6 +76,7 @@ class DynamicGroup(PrimaryModel):
76
76
 
77
77
  objects = BaseManager.from_queryset(DynamicGroupQuerySet)()
78
78
  is_dynamic_group_associable_model = False
79
+ is_data_compliance_model = False
79
80
 
80
81
  clone_fields = ["content_type", "group_type", "filter", "tenant"]
81
82
 
@@ -1255,6 +1256,7 @@ class StaticGroupAssociation(OrganizationalModel):
1255
1256
  is_contact_associable_model = False
1256
1257
  is_dynamic_group_associable_model = False
1257
1258
  is_saved_view_model = False
1259
+ is_data_compliance_model = False
1258
1260
 
1259
1261
  class Meta:
1260
1262
  unique_together = [["dynamic_group", "associated_object_type", "associated_object_id"]]
@@ -15,7 +15,7 @@ from django.contrib.contenttypes.models import ContentType
15
15
  from django.core.exceptions import ValidationError
16
16
  from django.core.validators import MinValueValidator
17
17
  from django.db import models, transaction
18
- from django.db.models import ProtectedError, signals
18
+ from django.db.models import Count, ProtectedError, Q, signals
19
19
  from django.utils import timezone
20
20
  from django.utils.functional import cached_property
21
21
  from django_celery_beat.clockedschedule import clocked
@@ -29,8 +29,10 @@ from nautobot.core.celery import (
29
29
  setup_nautobot_job_logging,
30
30
  )
31
31
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
32
+ from nautobot.core.events import publish_event
32
33
  from nautobot.core.models import BaseManager, BaseModel
33
34
  from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
35
+ from nautobot.core.models.utils import serialize_object_v2
34
36
  from nautobot.core.utils.logging import sanitize
35
37
  from nautobot.extras.choices import (
36
38
  ButtonClassChoices,
@@ -48,7 +50,13 @@ from nautobot.extras.constants import (
48
50
  )
49
51
  from nautobot.extras.managers import JobResultManager, ScheduledJobsManager
50
52
  from nautobot.extras.models import ChangeLoggedModel, GitRepository
51
- from nautobot.extras.models.mixins import ContactMixin, DynamicGroupsModelMixin, NotesMixin, SavedViewMixin
53
+ from nautobot.extras.models.mixins import (
54
+ ApprovableModelMixin,
55
+ ContactMixin,
56
+ DynamicGroupsModelMixin,
57
+ NotesMixin,
58
+ SavedViewMixin,
59
+ )
52
60
  from nautobot.extras.querysets import JobQuerySet, ScheduledJobExtendedQuerySet
53
61
  from nautobot.extras.utils import (
54
62
  ChangeLoggedModelsQuery,
@@ -153,9 +161,6 @@ class Job(PrimaryModel):
153
161
 
154
162
  # Additional properties, potentially inherited from the source code
155
163
  # See also the docstring of nautobot.extras.jobs.BaseJob.Meta.
156
- approval_required = models.BooleanField(
157
- default=False, help_text="Whether the job requires approval from another user before running"
158
- )
159
164
  hidden = models.BooleanField(
160
165
  default=False,
161
166
  db_index=True,
@@ -214,10 +219,6 @@ class Job(PrimaryModel):
214
219
  default=False,
215
220
  help_text="If set, the configured description will remain even if the underlying Job source code changes",
216
221
  )
217
- approval_required_override = models.BooleanField(
218
- default=False,
219
- help_text="If set, the configured value will remain even if the underlying Job source code changes",
220
- )
221
222
  dryrun_default_override = models.BooleanField(
222
223
  default=False,
223
224
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
@@ -251,6 +252,7 @@ class Job(PrimaryModel):
251
252
  help_text="If set, the configured value will remain even if the underlying Job source code changes",
252
253
  )
253
254
  objects = BaseManager.from_queryset(JobQuerySet)()
255
+ is_data_compliance_model = False
254
256
 
255
257
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
256
258
 
@@ -314,7 +316,7 @@ class Job(PrimaryModel):
314
316
 
315
317
  @property
316
318
  def runnable(self):
317
- return self.enabled and self.installed and not (self.has_sensitive_variables and self.approval_required)
319
+ return self.enabled and self.installed
318
320
 
319
321
  @cached_property
320
322
  def git_repository(self):
@@ -389,11 +391,6 @@ class Job(PrimaryModel):
389
391
  if len(self.name) > JOB_MAX_NAME_LENGTH:
390
392
  raise ValidationError(f"Name may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
391
393
 
392
- if self.has_sensitive_variables is True and self.approval_required is True:
393
- raise ValidationError(
394
- {"approval_required": "A job that may have sensitive variables cannot be marked as requiring approval"}
395
- )
396
-
397
394
  def save(self, *args, **kwargs):
398
395
  """When a Job is uninstalled, auto-disable all associated JobButtons, JobHooks, and ScheduledJobs."""
399
396
  super().save(*args, **kwargs)
@@ -443,6 +440,8 @@ class JobHook(OrganizationalModel):
443
440
  type_delete = models.BooleanField(default=False, help_text="Call this job hook when a matching object is deleted.")
444
441
  type_update = models.BooleanField(default=False, help_text="Call this job hook when a matching object is updated.")
445
442
 
443
+ is_data_compliance_model = False
444
+
446
445
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobhook.html"
447
446
 
448
447
  class Meta:
@@ -533,8 +532,10 @@ class JobLogEntry(BaseModel):
533
532
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
534
533
 
535
534
  is_metadata_associable_model = False
535
+ is_data_compliance_model = False
536
536
 
537
537
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
538
+ hide_in_diff_view = True
538
539
 
539
540
  def __str__(self):
540
541
  return self.message
@@ -583,6 +584,7 @@ class JobQueue(PrimaryModel):
583
584
  )
584
585
 
585
586
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobqueue.html"
587
+ is_data_compliance_model = False
586
588
 
587
589
  class Meta:
588
590
  ordering = ["name"]
@@ -613,6 +615,7 @@ class JobQueueAssignment(BaseModel):
613
615
  job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name="job_queue_assignments")
614
616
  job_queue = models.ForeignKey(JobQueue, on_delete=models.CASCADE, related_name="job_assignments")
615
617
  is_metadata_associable_model = False
618
+ is_data_compliance_model = False
616
619
 
617
620
  class Meta:
618
621
  unique_together = ["job", "job_queue"]
@@ -681,10 +684,17 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
681
684
  traceback = models.TextField(blank=True, null=True) # noqa: DJ001 # django-nullable-model-string-field -- TODO: can we remove null=True?
682
685
  meta = models.JSONField(null=True, default=None, editable=False)
683
686
  scheduled_job = models.ForeignKey(to="extras.ScheduledJob", on_delete=models.SET_NULL, null=True, blank=True)
687
+ debug_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
688
+ success_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
689
+ info_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
690
+ warning_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
691
+ error_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
684
692
 
685
693
  objects = JobResultManager()
686
694
 
687
695
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
696
+ hide_in_diff_view = True
697
+ is_data_compliance_model = False
688
698
 
689
699
  def __init__(self, *args, **kwargs):
690
700
  super().__init__(*args, **kwargs)
@@ -764,6 +774,30 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
764
774
 
765
775
  set_status.alters_data = True
766
776
 
777
+ def count_logs_by_level(self):
778
+ """Helper method to count JobLogEntries after a Job is run, or update these values when missing or changed."""
779
+ db_log_counts = self.job_log_entries.aggregate(
780
+ debug_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_DEBUG)),
781
+ success_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_SUCCESS)),
782
+ info_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_INFO)),
783
+ warning_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_WARNING)),
784
+ error_log_count=Count(
785
+ "pk",
786
+ filter=Q(
787
+ log_level__in=[
788
+ LogLevelChoices.LOG_FAILURE,
789
+ LogLevelChoices.LOG_ERROR,
790
+ LogLevelChoices.LOG_CRITICAL,
791
+ ]
792
+ ),
793
+ ),
794
+ )
795
+ self.debug_log_count = db_log_counts["debug_log_count"]
796
+ self.success_log_count = db_log_counts["success_log_count"]
797
+ self.info_log_count = db_log_counts["info_log_count"]
798
+ self.warning_log_count = db_log_counts["warning_log_count"]
799
+ self.error_log_count = db_log_counts["error_log_count"]
800
+
767
801
  @classmethod
768
802
  def execute_job(cls, *args, **kwargs):
769
803
  """
@@ -862,6 +896,7 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
862
896
  # And from the kubernetes pod, we specify "--local"/synchronous=True
863
897
  # so that `run_kubernetes_job_and_return_job_result` is not executed again and the job will be run locally.
864
898
  if job_queue.queue_type == JobQueueTypeChoices.TYPE_KUBERNETES and not synchronous:
899
+ # TODO: make this branch aware!
865
900
  return run_kubernetes_job_and_return_job_result(job_queue, job_result, json.dumps(job_kwargs))
866
901
 
867
902
  job_celery_kwargs = {
@@ -1007,6 +1042,18 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
1007
1042
 
1008
1043
  log.alters_data = True
1009
1044
 
1045
+ def save(self, *args, **kwargs):
1046
+ """When a JobResult is saved and in a terminal state, store missing log counts for summary."""
1047
+ if self.status in JobResultStatusChoices.READY_STATES and None in [
1048
+ self.debug_log_count,
1049
+ self.info_log_count,
1050
+ self.success_log_count,
1051
+ self.warning_log_count,
1052
+ self.error_log_count,
1053
+ ]:
1054
+ self.count_logs_by_level()
1055
+ super().save(*args, **kwargs)
1056
+
1010
1057
 
1011
1058
  #
1012
1059
  # Job Button
@@ -1056,10 +1103,18 @@ class JobButton(ContactMixin, ChangeLoggedModel, DynamicGroupsModelMixin, NotesM
1056
1103
  )
1057
1104
 
1058
1105
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobbutton.html"
1106
+ is_data_compliance_model = False
1059
1107
 
1060
1108
  class Meta:
1061
1109
  ordering = ["group_name", "weight", "name"]
1062
1110
 
1111
+ @property
1112
+ def button_class_css_class(self):
1113
+ """Map self.button_class database value to the correct CSS class for buttons."""
1114
+ if self.button_class == ButtonClassChoices.CLASS_DEFAULT:
1115
+ return "secondary"
1116
+ return self.button_class
1117
+
1063
1118
  def __str__(self):
1064
1119
  return self.name
1065
1120
 
@@ -1082,6 +1137,7 @@ class ScheduledJobs(models.Model):
1082
1137
  last_update = models.DateTimeField(null=False)
1083
1138
 
1084
1139
  objects = ScheduledJobsManager()
1140
+ is_data_compliance_model = False
1085
1141
 
1086
1142
  def __str__(self):
1087
1143
  return str(self.ident)
@@ -1114,7 +1170,7 @@ class ScheduledJobs(models.Model):
1114
1170
  return None
1115
1171
 
1116
1172
 
1117
- class ScheduledJob(BaseModel):
1173
+ class ScheduledJob(ApprovableModelMixin, BaseModel):
1118
1174
  """Model representing a periodic task."""
1119
1175
 
1120
1176
  name = models.CharField(
@@ -1199,22 +1255,14 @@ class ScheduledJob(BaseModel):
1199
1255
  null=True,
1200
1256
  help_text="User that requested the schedule",
1201
1257
  )
1202
- approved_by_user = models.ForeignKey(
1203
- to=settings.AUTH_USER_MODEL,
1204
- on_delete=models.SET_NULL,
1205
- related_name="+",
1206
- blank=True,
1207
- null=True,
1208
- help_text="User that approved the schedule",
1209
- )
1210
1258
  # todoindex:
1211
1259
  approval_required = models.BooleanField(default=False)
1212
- approved_at = models.DateTimeField(
1260
+ decision_date = models.DateTimeField(
1213
1261
  editable=False,
1214
1262
  blank=True,
1215
1263
  null=True,
1216
- verbose_name="Approval date/time",
1217
- help_text="Datetime that the schedule was approved",
1264
+ verbose_name="Approval/Rejection date/time",
1265
+ help_text="Datetime that the schedule was approved or denied",
1218
1266
  )
1219
1267
  crontab = models.CharField(
1220
1268
  max_length=CHARFIELD_MAX_LENGTH,
@@ -1227,6 +1275,7 @@ class ScheduledJob(BaseModel):
1227
1275
  no_changes = False
1228
1276
 
1229
1277
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html"
1278
+ is_data_compliance_model = False
1230
1279
 
1231
1280
  def __str__(self):
1232
1281
  return f"{self.name}: {self.interval}"
@@ -1257,18 +1306,43 @@ class ScheduledJob(BaseModel):
1257
1306
  self.last_run_at = self.start_time - timedelta(
1258
1307
  **{JobExecutionType.CELERY_INTERVAL_MAP[self.interval]: multiplier},
1259
1308
  )
1260
-
1309
+ is_new = not self.present_in_database
1261
1310
  super().save(*args, **kwargs)
1311
+ if is_new:
1312
+ self.begin_approval_workflow()
1262
1313
 
1263
- def clean(self):
1314
+ def on_workflow_initiated(self, approval_workflow):
1315
+ """When initiated, set approval required to True."""
1316
+ self.approval_required = True
1317
+ self.save()
1318
+
1319
+ def on_workflow_approved(self, approval_workflow):
1320
+ """When approved, set decision_date to decision_date from approval workflow."""
1321
+ self.decision_date = approval_workflow.decision_date
1322
+ self.save()
1323
+
1324
+ publish_event_payload = {"data": serialize_object_v2(self)}
1325
+ publish_event(topic="nautobot.jobs.approval.approved", payload=publish_event_payload)
1326
+
1327
+ def on_workflow_denied(self, approval_workflow):
1328
+ """When denied, set decision_date to decision_date from approval workflow."""
1329
+ self.decision_date = approval_workflow.decision_date
1330
+ self.save()
1331
+
1332
+ publish_event_payload = {"data": serialize_object_v2(self)}
1333
+ publish_event(topic="nautobot.jobs.approval.denied", payload=publish_event_payload)
1334
+
1335
+ def get_approval_template(self):
1264
1336
  """
1265
- Model Validation
1337
+ Return a custom template path to be used for the approval UI.
1338
+
1339
+ This allows the object to override the default approval template
1340
+ when special logic or warnings are needed. If no override is
1341
+ required, return None to use the standard approval form.
1266
1342
  """
1267
- if self.user and self.approved_by_user and self.user == self.approved_by_user:
1268
- raise ValidationError("The requesting and approving users cannot be the same")
1269
- # bitwise xor also works on booleans, but not on complex values
1270
- if bool(self.approved_by_user) ^ bool(self.approved_at):
1271
- raise ValidationError("Approval by user and approval time must either both be set or both be undefined")
1343
+ if self.one_off and self.start_time < timezone.now():
1344
+ return "extras/job_approval_confirmation.html"
1345
+ return None
1272
1346
 
1273
1347
  @property
1274
1348
  def schedule(self):
@@ -1283,6 +1357,10 @@ class ScheduledJob(BaseModel):
1283
1357
  """Deprecated backward-compatibility property for the queue name this job is scheduled for."""
1284
1358
  return self.job_queue.name if self.job_queue else ""
1285
1359
 
1360
+ @property
1361
+ def runnable(self):
1362
+ return self.job_model.runnable and not (self.job_model.has_sensitive_variables and self.approval_required)
1363
+
1286
1364
  @queue.setter
1287
1365
  def queue(self, value: str):
1288
1366
  if value:
@@ -1332,10 +1410,10 @@ class ScheduledJob(BaseModel):
1332
1410
  interval: str = JobExecutionType.TYPE_IMMEDIATELY,
1333
1411
  crontab: str = "",
1334
1412
  profile: bool = False,
1335
- approval_required: bool = False,
1336
1413
  job_queue: Optional[JobQueue] = None,
1337
1414
  task_queue: Optional[str] = None, # deprecated!
1338
1415
  ignore_singleton_lock: bool = False,
1416
+ validated_save: bool = True,
1339
1417
  **job_kwargs,
1340
1418
  ):
1341
1419
  """
@@ -1354,7 +1432,6 @@ class ScheduledJob(BaseModel):
1354
1432
  Defaults to JobExecutionType.TYPE_IMMEDIATELY.
1355
1433
  crontab (str): The crontab string for the schedule. Defaults to "".
1356
1434
  profile (bool): Flag indicating whether to profile the job. Defaults to False.
1357
- approval_required (bool): Flag indicating if approval is required. Defaults to False.
1358
1435
  job_queue (JobQueue): The Job queue to use. If unset, use the configured default celery queue.
1359
1436
  task_queue (str): The queue name to use. **Deprecated, prefer `job_queue`.**
1360
1437
  ignore_singleton_lock (bool): Whether to ignore singleton locks. Defaults to False.
@@ -1388,6 +1465,12 @@ class ScheduledJob(BaseModel):
1388
1465
  "queue": task_queue,
1389
1466
  "nautobot_job_ignore_singleton_lock": ignore_singleton_lock,
1390
1467
  }
1468
+ if "nautobot_version_control" in settings.PLUGINS:
1469
+ from nautobot_version_control.utils import active_branch # pylint: disable=import-error
1470
+
1471
+ branch_name = active_branch()
1472
+ # TODO: what do we do when merging a branch's ScheduledJob down to main?
1473
+ celery_kwargs["nautobot_job_branch_name"] = branch_name
1391
1474
  if job_model.soft_time_limit > 0:
1392
1475
  celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
1393
1476
  if job_model.time_limit > 0:
@@ -1417,11 +1500,11 @@ class ScheduledJob(BaseModel):
1417
1500
  interval=interval,
1418
1501
  one_off=(interval == JobExecutionType.TYPE_FUTURE),
1419
1502
  user=user,
1420
- approval_required=approval_required,
1421
1503
  crontab=crontab,
1422
1504
  job_queue=job_queue,
1423
1505
  )
1424
- scheduled_job.validated_save()
1506
+ if validated_save:
1507
+ scheduled_job.validated_save()
1425
1508
  return scheduled_job
1426
1509
 
1427
1510
  create_schedule.__func__.alters_data = True
@@ -12,6 +12,7 @@ from nautobot.core.models.fields import JSONArrayField
12
12
  from nautobot.core.models.generics import PrimaryModel
13
13
  from nautobot.core.models.querysets import RestrictedQuerySet
14
14
  from nautobot.core.settings_funcs import is_truthy
15
+ from nautobot.core.utils.cache import construct_cache_key
15
16
  from nautobot.extras.choices import MetadataTypeDataTypeChoices
16
17
  from nautobot.extras.models.change_logging import ChangeLoggedModel
17
18
  from nautobot.extras.models.contacts import Contact, Team
@@ -24,7 +25,7 @@ class MetadataTypeManager(BaseManager.from_queryset(RestrictedQuerySet)):
24
25
  def get_for_model(self, model):
25
26
  """Return all MetadataTypes assigned to the given model."""
26
27
  concrete_model = model._meta.concrete_model
27
- cache_key = f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
28
+ cache_key = construct_cache_key(self, method_name="get_for_model", model=concrete_model._meta.label_lower)
28
29
  queryset = cache.get(cache_key)
29
30
  if queryset is None:
30
31
  content_type = ContentType.objects.get_for_model(concrete_model)
@@ -32,8 +33,6 @@ class MetadataTypeManager(BaseManager.from_queryset(RestrictedQuerySet)):
32
33
  cache.set(cache_key, queryset)
33
34
  return queryset
34
35
 
35
- get_for_model.cache_key_prefix = "nautobot.extras.metadatatype.get_for_model"
36
-
37
36
 
38
37
  @extras_features(
39
38
  "custom_links",