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
@@ -4,7 +4,6 @@ Plugin utilities.
4
4
 
5
5
  import importlib.util
6
6
  import logging
7
- import sys
8
7
 
9
8
  from django.core.exceptions import ImproperlyConfigured
10
9
  from django.utils.module_loading import import_string
@@ -17,37 +16,6 @@ from .exceptions import PluginImproperlyConfigured, PluginNotFound
17
16
  logger = logging.getLogger(__name__)
18
17
 
19
18
 
20
- def import_object(module_and_object):
21
- """
22
- Import a specific object from a specific module by name, such as "nautobot.extras.plugins.utils.import_object".
23
-
24
- Returns the imported object, or None if it doesn't exist.
25
- """
26
- target_module_name, object_name = module_and_object.rsplit(".", 1)
27
- module_hierarchy = target_module_name.split(".")
28
-
29
- # Iterate through the module hierarchy, checking for the existence of each successive submodule.
30
- # We have to do this rather than jumping directly to calling find_spec(target_module_name)
31
- # because find_spec will raise a ModuleNotFoundError if any parent module of target_module_name does not exist.
32
- module_name = ""
33
- for module_component in module_hierarchy:
34
- module_name = f"{module_name}.{module_component}" if module_name else module_component
35
- spec = importlib.util.find_spec(module_name)
36
- if spec is None:
37
- # No such module
38
- return None
39
-
40
- # Okay, target_module_name exists. Load it if not already loaded
41
- if target_module_name in sys.modules:
42
- module = sys.modules[target_module_name]
43
- else:
44
- module = importlib.util.module_from_spec(spec)
45
- sys.modules[target_module_name] = module
46
- spec.loader.exec_module(module)
47
-
48
- return getattr(module, object_name, None)
49
-
50
-
51
19
  def load_plugins(settings):
52
20
  """Process plugins and log errors if they can't be loaded."""
53
21
  for plugin_name in settings.PLUGINS:
@@ -77,7 +45,7 @@ def load_plugin(plugin_name, settings):
77
45
  except AttributeError as err:
78
46
  raise PluginImproperlyConfigured(
79
47
  f"Plugin {plugin_name} does not provide a 'config' variable. This should be defined in the plugin's "
80
- f"__init__.py file and point to the PluginConfig subclass."
48
+ f"__init__.py file and point to the NautobotAppConfig subclass."
81
49
  ) from err
82
50
 
83
51
  # Validate user-provided configuration settings and assign defaults. Plugin
@@ -86,7 +86,6 @@ class InstalledAppsView(GenericView):
86
86
  """
87
87
 
88
88
  table = InstalledAppsTable
89
- breadcrumbs = Breadcrumbs(items={"*": [ViewNameBreadcrumbItem(view_name="apps:apps_list", label="Installed Apps")]})
90
89
  view_titles = Titles(titles={"*": "Installed Apps"})
91
90
 
92
91
  def get(self, request):
@@ -142,11 +141,6 @@ class InstalledAppDetailView(GenericView):
142
141
  items={
143
142
  "*": [
144
143
  ViewNameBreadcrumbItem(view_name="apps:apps_list", label="Installed Apps"),
145
- ViewNameBreadcrumbItem(
146
- view_name="apps:app_detail",
147
- reverse_kwargs=lambda context: {"app": context["app_data"]["package"]},
148
- label=lambda context: context["app_data"]["name"],
149
- ),
150
144
  ]
151
145
  }
152
146
  )
@@ -250,9 +244,6 @@ class MarketplaceView(GenericView):
250
244
  View for listing all available Apps.
251
245
  """
252
246
 
253
- breadcrumbs = Breadcrumbs(
254
- items={"generic": [ViewNameBreadcrumbItem(view_name="apps:apps_marketplace", label="Apps Marketplace")]}
255
- )
256
247
  view_titles = Titles(titles={"generic": "Apps Marketplace"})
257
248
 
258
249
  def get(self, request):
@@ -19,6 +19,9 @@ class ConfigContextQuerySet(RestrictedQuerySet):
19
19
  # `device_type` for Device; `type` for VirtualMachine
20
20
  device_type = getattr(obj, "device_type", None)
21
21
 
22
+ # `device_family` for Device;
23
+ device_family = getattr(device_type, "device_family", None)
24
+
22
25
  device_redundancy_group = getattr(obj, "device_redundancy_group", None)
23
26
 
24
27
  # Get the group of the assigned tenant, if any
@@ -40,6 +43,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
40
43
  Q(locations__in=locations) | Q(locations=None),
41
44
  Q(roles=role) | Q(roles=None),
42
45
  Q(device_types=device_type) | Q(device_types=None),
46
+ Q(device_families=device_family) | Q(device_families=None),
43
47
  Q(platforms=obj.platform) | Q(platforms=None),
44
48
  Q(device_redundancy_groups=device_redundancy_group) | Q(device_redundancy_groups=None),
45
49
  Q(tenant_groups__in=tenant_groups) | Q(tenant_groups=None),
@@ -136,6 +140,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
136
140
  if self.model._meta.model_name == "device":
137
141
  location_query_string = "location"
138
142
  base_query.add((Q(device_types=OuterRef("device_type")) | Q(device_types=None)), Q.AND)
143
+ base_query.add((Q(device_families=OuterRef("device_type__device_family")) | Q(device_families=None)), Q.AND)
139
144
  base_query.add(
140
145
  (Q(device_redundancy_groups=OuterRef("device_redundancy_group")) | Q(device_redundancy_groups=None)),
141
146
  Q.AND,
@@ -145,6 +150,9 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
145
150
  base_query.add((Q(cluster_groups=OuterRef("clusters__cluster_group")) | Q(cluster_groups=None)), Q.AND)
146
151
  else:
147
152
  location_query_string = "cluster__location"
153
+ base_query.add(Q(device_types=None), Q.AND)
154
+ base_query.add(Q(device_families=None), Q.AND)
155
+ base_query.add(Q(device_redundancy_groups=None), Q.AND)
148
156
  # For virtual machines, handle cluster as ForeignKey relationship
149
157
  base_query.add((Q(clusters=OuterRef("cluster")) | Q(clusters=None)), Q.AND)
150
158
  base_query.add((Q(cluster_groups=OuterRef("cluster__cluster_group")) | Q(cluster_groups=None)), Q.AND)
@@ -24,7 +24,7 @@ from nautobot.core.celery import app, import_jobs
24
24
  from nautobot.core.models import BaseModel
25
25
  from nautobot.core.utils.cache import construct_cache_key
26
26
  from nautobot.core.utils.logging import sanitize
27
- from nautobot.extras.choices import JobResultStatusChoices, ObjectChangeActionChoices
27
+ from nautobot.extras.choices import ButtonClassChoices, JobResultStatusChoices, ObjectChangeActionChoices
28
28
  from nautobot.extras.constants import CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
29
29
  from nautobot.extras.models import (
30
30
  ComputedField,
@@ -557,24 +557,6 @@ m2m_changed.connect(dynamic_group_children_changed, sender=DynamicGroup.children
557
557
  pre_save.connect(dynamic_group_membership_created, sender=DynamicGroupMembership)
558
558
 
559
559
 
560
- def dynamic_group_update_cached_members(sender, instance, **kwargs):
561
- """
562
- When a DynamicGroup or DynamicGroupMembership is updated, update the cache of members for it and any parent groups.
563
- """
564
- if isinstance(instance, DynamicGroupMembership):
565
- group = instance.parent_group
566
- else:
567
- group = instance
568
-
569
- group.update_cached_members()
570
- for ancestor in group.get_ancestors():
571
- ancestor.update_cached_members()
572
-
573
-
574
- post_save.connect(dynamic_group_update_cached_members, sender=DynamicGroup)
575
- post_save.connect(dynamic_group_update_cached_members, sender=DynamicGroupMembership)
576
-
577
-
578
560
  #
579
561
  # Jobs
580
562
  #
@@ -659,6 +641,25 @@ def refresh_job_models(sender, *, apps, **kwargs):
659
641
  job_model.installed = False
660
642
  job_model.save()
661
643
 
644
+ # Wire up the JobButton for Dynamic Group member refresh
645
+ JobButton = apps.get_model("extras", "JobButton")
646
+ ContentType = apps.get_model("contenttypes", "ContentType") # pylint: disable=redefined-outer-name
647
+ DynamicGroup = apps.get_model("extras", "DynamicGroup") # pylint: disable=redefined-outer-name
648
+
649
+ dg_job_button, _ = JobButton.objects.get_or_create(
650
+ name="Refresh Dynamic Group Members Cache",
651
+ job=Job.objects.get(
652
+ module_name="nautobot.core.jobs.groups", job_class_name="RefreshDynamicGroupCacheJobButtonReceiver"
653
+ ),
654
+ defaults={
655
+ "enabled": True,
656
+ "text": "Refresh Members",
657
+ "button_class": ButtonClassChoices.CLASS_WARNING,
658
+ "confirmation": True,
659
+ },
660
+ )
661
+ dg_job_button.content_types.add(ContentType.objects.get_for_model(DynamicGroup))
662
+
662
663
 
663
664
  #
664
665
  # Metadata
nautobot/extras/tables.py CHANGED
@@ -1,13 +1,13 @@
1
+ import logging
2
+ from textwrap import dedent
3
+
1
4
  from django.conf import settings
2
- from django.db.models import QuerySet
5
+ from django.contrib.contenttypes.models import ContentType
3
6
  from django.utils.html import format_html, format_html_join
4
7
  import django_tables2 as tables
5
- from django_tables2.data import TableData
6
- from django_tables2.rows import BoundRows
7
8
  from django_tables2.utils import Accessor
8
9
  from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
9
10
 
10
- from nautobot.core.models.querysets import count_related
11
11
  from nautobot.core.tables import (
12
12
  ApprovalButtonsColumn,
13
13
  BaseTable,
@@ -24,7 +24,7 @@ from nautobot.core.tables import (
24
24
  from nautobot.core.templatetags.helpers import HTML_NONE, render_boolean, render_json, render_markdown
25
25
  from nautobot.tenancy.tables import TenantColumn
26
26
 
27
- from .choices import LogLevelChoices, MetadataTypeDataTypeChoices
27
+ from .choices import JobResultStatusChoices, MetadataTypeDataTypeChoices
28
28
  from .models import (
29
29
  ApprovalWorkflow,
30
30
  ApprovalWorkflowDefinition,
@@ -73,6 +73,8 @@ from .models import (
73
73
  )
74
74
  from .registry import registry
75
75
 
76
+ logger = logging.getLogger(__name__)
77
+
76
78
  APPROVAL_WORKFLOW_OBJECT = """
77
79
  {% if record.object_under_review and record.object_under_review.get_absolute_url %}
78
80
  <a href="{{ record.object_under_review.get_absolute_url }}">{{ record.object_under_review }}</a>
@@ -129,7 +131,7 @@ GITREPOSITORY_BUTTONS = """
129
131
  <button
130
132
  data-url="{% url 'extras:gitrepository_sync' pk=record.pk %}"
131
133
  type="submit"
132
- class="dropdown-item sync-repository{% if perms.extras.change_gitrepository %} text-primary"{% else %}" disabled="disabled"{% endif %}
134
+ class="dropdown-item sync-repository{% if perms.extras.change_gitrepository %} text-primary"{% else %}" disabled{% endif %}
133
135
  >
134
136
  <span class="mdi mdi-source-branch-sync" aria-hidden="true"></span>
135
137
  Sync
@@ -168,7 +170,7 @@ JOB_RESULT_BUTTONS = """
168
170
  </li>
169
171
  {% else %}
170
172
  <li>
171
- <a href="#" class="dropdown-item disabled">
173
+ <a class="dropdown-item disabled" aria-disabled="true">
172
174
  <span class="mdi mdi-repeat-off" aria-hidden="true"></span>
173
175
  Job is not available, cannot be re-run
174
176
  </a>
@@ -220,7 +222,7 @@ SCHEDULED_JOB_APPROVAL_QUEUE_BUTTONS = """
220
222
  <button
221
223
  type="button"
222
224
  onClick="handleDetailPostAction('{% url 'extras:scheduledjob_approval_request_view' pk=record.pk %}', '_dry_run')"
223
- class="dropdown-item{% if perms.extras.run_job and record.job_model.supports_dryrun %} text-primary"{% else %}" disabled="disabled"{% endif %}
225
+ class="dropdown-item{% if perms.extras.run_job and record.job_model.supports_dryrun %} text-primary"{% else %}" disabled{% endif %}
224
226
  >
225
227
  <span class="mdi mdi-play" aria-hidden="true"></span>
226
228
  Dry Run
@@ -230,7 +232,7 @@ SCHEDULED_JOB_APPROVAL_QUEUE_BUTTONS = """
230
232
  <button
231
233
  type="button"
232
234
  onClick="handleDetailPostAction('{% url 'extras:scheduledjob_approval_request_view' pk=record.pk %}', '_approve')"
233
- class="dropdown-item{% if perms.extras.run_job %} text-success"{% else %}" disabled="disabled"{% endif %}
235
+ class="dropdown-item{% if perms.extras.run_job %} text-success"{% else %}" disabled{% endif %}
234
236
  >
235
237
  <span class="mdi mdi-check" aria-hidden="true"></span>
236
238
  Approve
@@ -240,7 +242,7 @@ SCHEDULED_JOB_APPROVAL_QUEUE_BUTTONS = """
240
242
  <button
241
243
  type="button"
242
244
  onClick="handleDetailPostAction('{% url 'extras:scheduledjob_approval_request_view' pk=record.pk %}', '_deny')"
243
- class="dropdown-item{% if perms.extras.run_job %} text-danger"{% else %}" disabled="disabled"{% endif %}
245
+ class="dropdown-item{% if perms.extras.run_job %} text-danger"{% else %}" disabled{% endif %}
244
246
  >
245
247
  <span class="mdi mdi-close" aria-hidden="true"></span>
246
248
  Deny
@@ -294,7 +296,7 @@ class ApprovalWorkflowStageDefinitionTable(BaseTable):
294
296
  fields = (
295
297
  "pk",
296
298
  "approval_workflow_definition",
297
- "weight",
299
+ "sequence",
298
300
  "name",
299
301
  "min_approvers",
300
302
  "denial_message",
@@ -303,7 +305,7 @@ class ApprovalWorkflowStageDefinitionTable(BaseTable):
303
305
  default_columns = (
304
306
  "pk",
305
307
  "approval_workflow_definition",
306
- "weight",
308
+ "sequence",
307
309
  "name",
308
310
  "min_approvers",
309
311
  "denial_message",
@@ -1065,8 +1067,8 @@ def log_object_link(value, record):
1065
1067
 
1066
1068
  def log_entry_color_css(record):
1067
1069
  if record.log_level.lower() in ("failure", "error", "critical"):
1068
- return "danger"
1069
- return record.log_level.lower()
1070
+ return "table-danger"
1071
+ return "table-" + record.log_level.lower()
1070
1072
 
1071
1073
 
1072
1074
  class JobTable(BaseTable):
@@ -1159,6 +1161,7 @@ class JobTable(BaseTable):
1159
1161
 
1160
1162
  class JobHookTable(BaseTable):
1161
1163
  pk = ToggleColumn()
1164
+ enabled = BooleanColumn()
1162
1165
  name = tables.Column(linkify=True)
1163
1166
  content_types = tables.TemplateColumn(WEBHOOK_CONTENT_TYPES)
1164
1167
  job = tables.Column(linkify=True)
@@ -1270,62 +1273,20 @@ class JobResultTable(BaseTable):
1270
1273
  duration = tables.Column(orderable=False)
1271
1274
  actions = ButtonsColumn(JobResult, buttons=("delete",), prepend_template=JOB_RESULT_BUTTONS)
1272
1275
 
1273
- def __init__(self, *args, **kwargs):
1274
- super().__init__(*args, **kwargs)
1275
- # Only calculate log counts for "summary" column if it's actually visible.
1276
- if "summary" in self.columns and self.columns["summary"].visible and isinstance(self.data.data, QuerySet):
1277
- self.data = TableData.from_data(
1278
- self.data.data.annotate(
1279
- debug_log_count=count_related(
1280
- JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_DEBUG}
1281
- ),
1282
- success_log_count=count_related(
1283
- JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_SUCCESS}
1284
- ),
1285
- info_log_count=count_related(
1286
- JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_INFO}
1287
- ),
1288
- warning_log_count=count_related(
1289
- JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_WARNING}
1290
- ),
1291
- error_log_count=count_related(
1292
- JobLogEntry,
1293
- "job_result",
1294
- filter_dict={
1295
- "log_level__in": [
1296
- LogLevelChoices.LOG_FAILURE,
1297
- LogLevelChoices.LOG_ERROR,
1298
- LogLevelChoices.LOG_CRITICAL,
1299
- ],
1300
- },
1301
- ),
1302
- )
1303
- )
1304
- self.data.set_table(self)
1305
- self.rows = BoundRows(data=self.data, table=self, pinned_data=self.pinned_data)
1306
-
1307
1276
  def render_summary(self, record):
1308
1277
  """
1309
1278
  Define custom rendering for the summary column.
1310
1279
  """
1311
- # Normally the *_log_count attributes will be generated efficiently via queryset annotation in the view,
1312
- # however, we cannot assume that will always be the case. Calculate them inefficiently as a fallback.
1313
- if not hasattr(record, "debug_log_count"):
1314
- record.debug_log_count = record.job_log_entries.filter(log_level=LogLevelChoices.LOG_DEBUG).count()
1315
- if not hasattr(record, "success_log_count"):
1316
- record.success_log_count = record.job_log_entries.filter(log_level=LogLevelChoices.LOG_SUCCESS).count()
1317
- if not hasattr(record, "info_log_count"):
1318
- record.info_log_count = record.job_log_entries.filter(log_level=LogLevelChoices.LOG_INFO).count()
1319
- if not hasattr(record, "warning_log_count"):
1320
- record.warning_log_count = record.job_log_entries.filter(log_level=LogLevelChoices.LOG_WARNING).count()
1321
- if not hasattr(record, "error_log_count"):
1322
- record.error_log_count = record.job_log_entries.filter(
1323
- log_level__in=[
1324
- LogLevelChoices.LOG_FAILURE,
1325
- LogLevelChoices.LOG_ERROR,
1326
- LogLevelChoices.LOG_CRITICAL,
1327
- ]
1328
- ).count()
1280
+ # The *_log_count attributes will be calculated and updated at the end of a Job run when JobResult is saved.
1281
+ # If the values are not present due to a running Job or are missing in any field, skip display.
1282
+ if record.status not in JobResultStatusChoices.READY_STATES or None in [
1283
+ record.debug_log_count,
1284
+ record.success_log_count,
1285
+ record.info_log_count,
1286
+ record.warning_log_count,
1287
+ record.error_log_count,
1288
+ ]:
1289
+ return ""
1329
1290
 
1330
1291
  return format_html(
1331
1292
  """<label class="badge bg-secondary">{}</label>
@@ -1364,6 +1325,7 @@ class JobResultTable(BaseTable):
1364
1325
  "job_model",
1365
1326
  "user",
1366
1327
  "status",
1328
+ "summary",
1367
1329
  "actions",
1368
1330
  )
1369
1331
 
@@ -1517,12 +1479,20 @@ class ScheduledJobTable(BaseTable):
1517
1479
  pk = ToggleColumn()
1518
1480
  name = tables.Column(linkify=True)
1519
1481
  job_model = tables.Column(verbose_name="Job", linkify=True)
1482
+ enabled = BooleanColumn()
1520
1483
  interval = tables.Column(verbose_name="Execution Type")
1521
1484
  start_time = tables.DateTimeColumn(verbose_name="First Run", format=settings.SHORT_DATETIME_FORMAT)
1522
1485
  last_run_at = tables.DateTimeColumn(verbose_name="Most Recent Run", format=settings.SHORT_DATETIME_FORMAT)
1523
1486
  crontab = tables.Column()
1524
1487
  total_run_count = tables.Column(verbose_name="Total Run Count")
1525
1488
  actions = ButtonsColumn(ScheduledJob, buttons=("delete",), prepend_template=SCHEDULED_JOB_BUTTONS)
1489
+ approval_state = tables.Column(empty_values=[], orderable=False)
1490
+
1491
+ def render_approval_state(self, record):
1492
+ workflow = record.associated_approval_workflows.first()
1493
+ if workflow is not None:
1494
+ return format_html('<a href="{}">{}</a>', record.get_approval_workflow_url(), workflow.current_state)
1495
+ return HTML_NONE
1526
1496
 
1527
1497
  class Meta(BaseTable.Meta):
1528
1498
  model = ScheduledJob
@@ -1531,6 +1501,7 @@ class ScheduledJobTable(BaseTable):
1531
1501
  "name",
1532
1502
  "total_run_count",
1533
1503
  "job_model",
1504
+ "approval_state",
1534
1505
  "interval",
1535
1506
  "start_time",
1536
1507
  "last_run_at",
@@ -1542,6 +1513,8 @@ class ScheduledJobTable(BaseTable):
1542
1513
  "pk",
1543
1514
  "name",
1544
1515
  "job_model",
1516
+ "enabled",
1517
+ "approval_state",
1545
1518
  "interval",
1546
1519
  "last_run_at",
1547
1520
  "actions",
@@ -1588,8 +1561,31 @@ class ObjectChangeTable(BaseTable):
1588
1561
 
1589
1562
  def __init__(self, *args, **kwargs):
1590
1563
  super().__init__(*args, **kwargs)
1591
- # The `object_repr` column also uses the `changed_object` generic-foreign-key value
1592
- self.add_conditional_prefetch("object_repr", "changed_object")
1564
+ # Only prefetch if all content types are valid
1565
+ if all(ct.model_class() is not None for ct in ContentType.objects.all()):
1566
+ self.add_conditional_prefetch("object_repr", "changed_object")
1567
+ else:
1568
+ error_message = dedent("""\
1569
+ One or more ContentType entries in the database are invalid.
1570
+ This will likely cause performance degradation when viewing the Object Change log.
1571
+ An administrator can follow these steps to resolve common issues:
1572
+ - Run `nautobot-server remove_stale_contenttypes`
1573
+ - Run `nautobot-server migrate <app_label> zero` for any app labels which no longer exist
1574
+ - Manually dropping tables for any models which have been removed from Nautobot or its plugins from your database
1575
+ - Run ```
1576
+ from django.contrib.contenttypes.models import ContentType
1577
+ qs = ContentType.objects.filter(
1578
+ app_label__in=[
1579
+ "<app_label_of_removed_plugin_1>",
1580
+ "<app_label_of_removed_plugin_2>",
1581
+ ]
1582
+ ) | ContentType.objects.filter(model__icontains="<name_of_removed_model_1>")
1583
+ # Review the queryset before running delete
1584
+ qs.delete()
1585
+ ```
1586
+ Please ensure you fully understand the implications of these actions before proceeding.
1587
+ """)
1588
+ logger.warning(error_message)
1593
1589
 
1594
1590
 
1595
1591
  #
@@ -38,6 +38,8 @@ Ref: https://github.com/nautobot/nautobot/issues/1289
38
38
 
39
39
  _replace_links_{{ ajax_divname }}(url, div.getElementsByTagName('TH'));
40
40
  _replace_links_{{ ajax_divname }}(url, div.getElementsByClassName('pagination'));
41
+ /* Hook back up the JS for the "per_page" dropdown, if present for this table */
42
+ initializeResultPerPageSelection(div);
41
43
  }
42
44
  }
43
45
  };
@@ -2,12 +2,14 @@
2
2
  {% load helpers %}
3
3
 
4
4
  {% block buttons %}
5
- <div class="btn-group">
6
- <a class="btn {% if approval_view %}btn-primary{% else %}btn-secondary{% endif %}" href="{% url 'extras:approver_dashboard' %}">
7
- My Approvals
5
+ <div class="btn-group" role="group">
6
+ <a class="btn btn-primary{% if approval_view %} bg-primary nb-text-body-bg{% endif %}"
7
+ href="{% url 'extras:approver_dashboard' %}">
8
+ <span class="mdi mdi-checkbox-multiple-outline" aria-hidden="true"></span> My Approvals
8
9
  </a>
9
- <a class="btn {% if not approval_view %}btn-primary{% else %}btn-secondary{% endif %}" href="{% url 'extras:approvee_dashboard' %}">
10
- My Requests
10
+ <a class="btn btn-primary{% if not approval_view %} bg-primary nb-text-body-bg{% endif %}"
11
+ href="{% url 'extras:approvee_dashboard' %}">
12
+ <span class="mdi mdi-help-box-multiple-outline" aria-hidden="true"></span> My Requests
11
13
  </a>
12
14
  </div>
13
15
  {% endblock %}
@@ -9,7 +9,7 @@
9
9
  {% render_field form.name %}
10
10
  {% render_field form.model_content_type %}
11
11
  {% render_field form.model_constraints %}
12
- {% render_field form.priority %}
12
+ {% render_field form.weight %}
13
13
  </div>
14
14
  </div>
15
15
  <div class="card">
@@ -22,7 +22,9 @@
22
22
  <thead>
23
23
  <tr>
24
24
  {% for field in stage.visible_fields %}
25
- <th>{{ field.label|capfirst }}</th>
25
+ <th>
26
+ <span data-bs-toggle="tooltip" data-bs-placement="top" title="{{ field.help_text }}">{{ field.label|capfirst }}</span>
27
+ </th>
26
28
  {% endfor %}
27
29
  </tr>
28
30
  </thead>
@@ -3,19 +3,27 @@
3
3
 
4
4
  {% block buttons %}
5
5
  <span title="{% if not object.is_active_stage %}This stage is not active{% elif request.user in object.users_that_already_approved %}You already approved this stage{% else %}Approve this stage{% endif %}">
6
- <a
7
- href="{% url 'extras:approvalworkflowstage_approve' object.pk %}?return_url={{request.path}}"
8
- class="btn btn-success{% if not object.is_active_stage or request.user in object.users_that_already_approved %} disabled{% endif %}"
9
- >
10
- <span class="mdi mdi-check"></span> Approve
11
- </a>
6
+ {% if object.is_active_stage and request.user not in object.users_that_already_approved %}
7
+ <a href="{% url 'extras:approvalworkflowstage_approve' object.pk %}?return_url={{request.path}}"
8
+ class="btn btn-success">
9
+ <span class="mdi mdi-check"></span> Approve
10
+ </a>
11
+ {% else %}
12
+ <a aria-disabled="true" class="btn btn-success disabled">
13
+ <span class="mdi mdi-check"></span> Approve
14
+ </a>
15
+ {% endif %}
12
16
  </span>
13
17
  <span title="{% if not object.is_active_stage %}This stage is not active{% elif request.user in object.users_that_already_approved %}You already approved this stage{% else %}Deny this stage{% endif %}">
14
- <a
15
- href="{% url 'extras:approvalworkflowstage_deny' object.pk %}?return_url={{request.path}}"
16
- class="btn btn-danger {% if not object.is_active_stage or request.user in object.users_that_already_approved %} disabled{% endif %}"
17
- >
18
- <span class="mdi mdi-close"></span> Deny
19
- </a>
18
+ {% if object.is_active_stage and request.user not in object.users_that_already_approved %}
19
+ <a href="{% url 'extras:approvalworkflowstage_deny' object.pk %}?return_url={{request.path}}"
20
+ class="btn btn-danger">
21
+ <span class="mdi mdi-close"></span> Deny
22
+ </a>
23
+ {% else %}
24
+ <a aria-disabled="true" class="btn btn-danger disabled">
25
+ <span class="mdi mdi-close"></span> Deny
26
+ </a>
27
+ {% endif %}
20
28
  </span>
21
29
  {% endblock buttons %}
@@ -18,6 +18,7 @@
18
18
  {% render_field form.locations %}
19
19
  {% render_field form.roles %}
20
20
  {% render_field form.device_types %}
21
+ {% render_field form.device_families %}
21
22
  {% render_field form.platforms %}
22
23
  {% render_field form.cluster_groups %}
23
24
  {% render_field form.clusters %}
@@ -1,6 +1,6 @@
1
- {% extends 'extras/configcontextschema.html' %}
1
+ {% extends 'extras/configcontextschema_retrieve.html' %}
2
2
 
3
- <h1>{% block title %}{{ object }} - Validation{% endblock %}</h1>
3
+ {% block title %}{{ object }} - Validation{% endblock %}
4
4
 
5
5
  {% block content %}
6
6
  <div class="row">
@@ -11,15 +11,21 @@
11
11
 
12
12
  {% block extra_tab_content %}
13
13
  <div id="members" role="tabpanel" class="tab-pane {% if not active_tab and not request.GET.tab or request.GET.tab == "members" %}active{% else %}fade{% endif %}">
14
- {% if members_list_url %}
15
- <div class="row">
16
- <div class="col-lg-12">
14
+ <div class="row">
15
+ <div class="col-lg-12">
16
+ {% if members_list_url %}
17
17
  <div class="alert alert-success" role="alert">
18
18
  You can bulk-add and bulk-remove members of this group from the <a href="{{ members_list_url }}">{{ members_verbose_name_plural|bettertitle }} list view</a>.
19
19
  </div>
20
- </div>
20
+ {% else %}
21
+ <div class="alert alert-warning" role="alert">
22
+ Dynamic group membership is cached for performance reasons, therefore this listing may not always
23
+ be up-to-date.<br>You can refresh the membership of this group asynchronously by clicking the
24
+ "Refresh Members" button above.
25
+ </div>
26
+ {% endif %}
21
27
  </div>
22
- {% endif %}
28
+ </div>
23
29
  <div class="row">
24
30
  <div class="col-lg-12">
25
31
  {% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='Dynamic Group members' %}
@@ -43,7 +43,7 @@
43
43
  <div class="tab-content">
44
44
  <div class="tab-pane active" id="filter-form">
45
45
  {% if filter_form %}
46
- <span class="form-text">
46
+ <span class="form-text mb-12">
47
47
  Select the filtering criteria to determine membership of objects matching
48
48
  the Content Type for this Dynamic Group. Fields that are not a dropdown are
49
49
  expected to have string inputs and do not support multiple values.
@@ -3,8 +3,6 @@
3
3
  {% load log_levels %}
4
4
  {% load static %}
5
5
 
6
- {% block title %}{{ block.super }} - Synchronization Status{% endblock %}
7
-
8
6
  {% block content %}
9
7
  <div class="row">
10
8
  <div class="col-lg-12">
@@ -10,15 +10,29 @@
10
10
  {% endif %}
11
11
  {% if "approve" in buttons and have_permission and record.is_active_stage %}
12
12
  <span title='{% if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Approve this stage{% endif %}'>
13
- <a href="{% url approval_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}" class="btn btn-success btn-sm{% if request.user in record.users_that_already_approved %} disabled{% endif %}">
14
- <span class="mdi mdi-check"></span>
15
- </a>
13
+ {% if request.user not in record.users_that_already_approved %}
14
+ <a href="{% url approval_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
15
+ class="btn btn-success btn-sm">
16
+ <span class="mdi mdi-check"></span>
17
+ </a>
18
+ {% else %}
19
+ <a aria-disabled="true" class="btn btn-success btn-sm disabled">
20
+ <span class="mdi mdi-check"></span>
21
+ </a>
22
+ {% endif %}
16
23
  </span>
17
24
  {% endif %}
18
25
  {% if "deny" in buttons and have_permission and record.is_active_stage and request.user not in record.users_that_already_approved %}
19
26
  <span title='{%if request.user in record.users_that_already_approved %}You already approved this stage{% else %}Deny this stage{% endif %}'>
20
- <a href="{% url deny_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}" class="btn btn-sm btn-danger {% if request.user in record.users_that_already_approved %} disabled{% endif %}">
21
- <span class="mdi mdi-close"></span>
22
- </a>
27
+ {% if request.user not in record.users_that_already_approved %}
28
+ <a href="{% url deny_route pk=record.pk %}?return_url={{ request.path }}{{ return_url_extra }}"
29
+ class="btn btn-danger btn-sm">
30
+ <span class="mdi mdi-close"></span>
31
+ </a>
32
+ {% else %}
33
+ <a aria-disabled="true" class="btn btn-danger btn-sm disabled">
34
+ <span class="mdi mdi-close"></span>
35
+ </a>
36
+ {% endif %}
23
37
  </span>
24
38
  {% endif %}