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
@@ -13,7 +13,8 @@ class ListViewFilterTestCase(SeleniumTestCase):
13
13
 
14
14
  def setUp(self):
15
15
  super().setUp()
16
- self.login(self.user.username, self.password)
16
+ self.login_as_superuser()
17
+
17
18
  if not LocationType.objects.filter(name="Campus").exists():
18
19
  LocationTypeFactory.create_batch(7)
19
20
  lt1 = LocationType.objects.get(name="Campus")
@@ -32,9 +33,6 @@ class ListViewFilterTestCase(SeleniumTestCase):
32
33
  Location.objects.create(
33
34
  name="Filter Test Location 5", location_type=lt4, parent=building_loc, status=location_status
34
35
  )
35
- # set test user to admin
36
- self.user.is_superuser = True
37
- self.user.save()
38
36
 
39
37
  self.cf_text_field_label = "Text Field"
40
38
  self.cf_integer_field_label = "Integer Field"
@@ -55,32 +53,28 @@ class ListViewFilterTestCase(SeleniumTestCase):
55
53
  CustomFieldChoice.objects.create(custom_field=self.custom_fields[2], value=f"SingleSelect Option {x}")
56
54
  CustomFieldChoice.objects.create(custom_field=self.custom_fields[3], value=f"MultiSelect Option {x}")
57
55
 
58
- def tearDown(self):
59
- self.logout()
60
- super().tearDown()
61
-
62
56
  def test_list_view_filter(self):
63
57
  """
64
- Test adding a filter with the list view filter modal and then
58
+ Test adding a filter with the list view filter drawer and then
65
59
  removing the filter with the list view "filters" ui element.
66
60
  """
67
61
 
68
62
  # retrieve location list view
69
63
  self.browser.visit(f"{self.live_server_url}{reverse('dcim:location_list')}")
70
- filter_modal = self.browser.find_by_id("FilterForm_modal", wait_time=10)
71
- self.assertFalse(filter_modal.visible)
64
+ filter_drawer = self.browser.find_by_id("FilterForm_drawer", wait_time=10)
65
+ self.assertFalse(filter_drawer.visible)
72
66
 
73
- # click on filter button to open the filter modal
67
+ # click on filter button to open the filter drawer
74
68
  filter_button = self.browser.find_by_id("id__filterbtn", wait_time=10)
75
69
  filter_button.click()
76
70
 
77
- # assert the filter modal has appeared
78
- self.assertTrue(filter_modal.visible)
71
+ # assert the filter drawer has appeared
72
+ self.assertTrue(filter_drawer.visible)
79
73
 
80
74
  # start typing a parent into select2
81
75
  location_type = LocationType.objects.filter(parent__isnull=True).first()
82
76
  parent = Location.objects.filter(location_type=location_type).first()
83
- parent_select = filter_modal.find_by_xpath(
77
+ parent_select = filter_drawer.find_by_xpath(
84
78
  "//label[@for='id_parent']/..//input[@class='select2-search__field']", wait_time=10
85
79
  )
86
80
  for _ in parent_select.type(f"{parent.name[:4]}", slowly=True):
@@ -91,38 +85,56 @@ class ListViewFilterTestCase(SeleniumTestCase):
91
85
  parent_option = self.browser.find_by_xpath(parent_option_xpath, wait_time=10)
92
86
  parent_option.click()
93
87
 
94
- # click apply button in filter modal
95
- apply_button = filter_modal.find_by_xpath("//div[@id='default-filter']//button[@type='submit']", wait_time=10)
88
+ # click apply button in filter drawer
89
+ apply_button = filter_drawer.find_by_xpath("//div[@id='default-filter']//button[@type='submit']", wait_time=10)
96
90
  apply_button.click()
97
91
 
98
92
  # assert the url has changed to add the filter param
99
93
  filtered_locations_url = self.browser.url
100
94
  self.assertIn("parent=", filtered_locations_url)
101
95
 
96
+ # go to advanced tab
97
+ self.browser.find_by_xpath("//a[@href='#advanced-filter']").click()
102
98
  # find and click the remove all filter X button
103
99
  remove_all_filters = self.browser.find_by_xpath(
104
- "//div[@class='filters-applied']//span[@class='filter-selection']//span[@class='remove-filter-param']",
100
+ "//div[contains(@class, 'nb-dynamic-filter-items')]//span[@class='badge nb-multi-badge']//button[@class='nb-dynamic-filter-remove']",
105
101
  wait_time=10,
106
102
  )
107
103
  remove_all_filters.click()
104
+ # apply filters removal
105
+ apply_button = self.browser.find_by_xpath(
106
+ "//section[@id='FilterForm_drawer']//div[@id='advanced-filter']//button[@type='submit']", wait_time=10
107
+ )
108
+ apply_button.click()
108
109
 
109
110
  # assert the filter param has been removed from the url
110
111
  self.assertNotIn("parent=", self.browser.url)
111
112
 
112
113
  # navigate back to the filter page and try the individual filter remove X button
113
- self.browser.visit(filtered_locations_url)
114
+ self.browser.back()
115
+ # go to advanced tab
116
+ self.browser.find_by_xpath("//a[@href='#advanced-filter']").click()
114
117
  remove_single_filter = self.browser.find_by_xpath(
115
- "//div[@class='filters-applied']//span[@class='filter-selection']//"
116
- "span[@class='filter-selection-choice-remove remove-filter-param']",
118
+ "//div[contains(@class, 'nb-dynamic-filter-items')]//span[@class='badge nb-multi-badge']//"
119
+ "span[@class='nb-multi-badge-items']//span[@class='badge']//button[@class='nb-dynamic-filter-remove']",
117
120
  wait_time=10,
118
121
  )
119
122
  remove_single_filter.click()
123
+ # apply filters removal
124
+ apply_button = self.browser.find_by_xpath(
125
+ "//section[@id='FilterForm_drawer']//div[@id='advanced-filter']//button[@type='submit']", wait_time=10
126
+ )
127
+ apply_button.click()
120
128
 
121
129
  # assert the filter has been removed from the url
122
130
  self.assertNotIn("parent=", self.browser.url)
123
131
 
124
- # assert the filter UI element is gone
125
- self.assertTrue(self.browser.is_element_not_present_by_xpath("//div[@class='filters-applied']"))
132
+ # assert the filter orange dot indicator is gone
133
+ self.assertTrue(
134
+ self.browser.is_element_not_present_by_xpath(
135
+ "//button[@id='id__filterbtn']//span[@aria-hidden='true' and @style]"
136
+ )
137
+ )
126
138
 
127
139
  def change_field_value(self, field_name, value, field_type="input", idx=0, select2_field_name=None):
128
140
  """Change the value of an input or select field.
@@ -142,7 +154,7 @@ class ListViewFilterTestCase(SeleniumTestCase):
142
154
  select2_field_name = select2_field_name or field_name
143
155
  select_field_xpath = (
144
156
  f"//ul[@id='select2-id_{select2_field_name}-results']/li[contains(@class,"
145
- f"'select2-results__option') and contains(text(),'{value}')]"
157
+ f"'select2-results__option') and text()='{value}']"
146
158
  )
147
159
  self.browser.find_by_xpath(select_field_xpath).click()
148
160
 
@@ -156,67 +168,117 @@ class ListViewFilterTestCase(SeleniumTestCase):
156
168
  multi_select_field_name = self.custom_fields[3].add_prefix_to_cf_key()
157
169
  apply_btn_xpath = "//div[@id='default-filter']//button[@type='submit']"
158
170
 
159
- # Open the filter modal, configure filter and apply filter
171
+ # Open the filter drawer, configure filter and apply filter
160
172
  self.browser.find_by_id("id__filterbtn").click()
173
+ self.browser.execute_script(
174
+ f"document.querySelector('[name={text_field_name}]').scrollIntoView({{ behavior: 'instant', block: 'end' }})"
175
+ )
161
176
  self.change_field_value(text_field_name, "example-text")
162
177
  self.change_field_value(integer_field_name, 4356)
163
178
  self.change_field_value(select_field_name, "SingleSelect Option A", field_type="select")
164
179
  self.change_field_value(multi_select_field_name, "MultiSelect Option A", field_type="select")
165
180
  self.browser.find_by_xpath(apply_btn_xpath).click() # Click on apply filter button
181
+ self.browser.find_by_xpath(
182
+ "//a[@href='#advanced-filter']"
183
+ ).click() # Go to Advanced tab to view applied filters badges
166
184
  self.assertTrue(self.browser.is_text_present("example-text"))
167
185
  self.assertTrue(self.browser.is_text_present("4356"))
168
186
  self.assertTrue(self.browser.is_text_present("SingleSelect Option A"))
169
187
  self.assertTrue(self.browser.is_text_present("MultiSelect Option A"))
170
188
 
171
189
  # Assert on update of field in Default Filter the update is replicated on Advanced Filter
172
- self.browser.find_by_id("id__filterbtn").click()
173
- self.change_field_value(text_field_name, "test new", idx=1)
174
- self.change_field_value(integer_field_name, 1111, idx=1)
190
+ self.browser.find_by_xpath("//a[@href='#default-filter']").click() # Go back to Basic tab
191
+ self.browser.execute_script(
192
+ f"document.querySelector('[name={text_field_name}]').scrollIntoView({{ behavior: 'instant', block: 'end' }})"
193
+ )
194
+ self.change_field_value(text_field_name, "test new")
195
+ self.change_field_value(integer_field_name, 1111)
175
196
  self.change_field_value(select_field_name, "SingleSelect Option B", field_type="select")
176
197
  self.change_field_value(multi_select_field_name, "MultiSelect Option B", field_type="select")
177
198
  self.browser.find_by_xpath("//a[@href='#advanced-filter']").click()
178
- self.assertEqual(self.browser.find_by_name(text_field_name)[2].value, "test new")
179
- self.assertEqual(self.browser.find_by_name(integer_field_name)[2].value, "1111")
180
- # CustomSelect Field is a MultiValueCharField, and the only way to get its values is using this approach.
181
- # Its values are the options available on the select field
182
- custom_select_field = self.browser.find_by_name(select_field_name)[2].find_by_tag("option")
183
- self.assertEqual(custom_select_field[0].value, "SingleSelect Option A")
184
- self.assertEqual(custom_select_field[1].value, "SingleSelect Option B")
185
- custom_multi_select_field = self.browser.find_by_name(multi_select_field_name)[2].find_by_tag("option")
186
- self.assertEqual(custom_multi_select_field[0].value, "MultiSelect Option A")
187
- self.assertEqual(custom_multi_select_field[1].value, "MultiSelect Option B")
199
+ self.assertTrue(
200
+ self.browser.is_element_present_by_xpath(
201
+ f"//span[@data-nb-field='{text_field_name}']//span[@data-nb-value='test new' and contains(text(),'test new')]"
202
+ )
203
+ )
204
+ self.assertTrue(
205
+ self.browser.is_element_present_by_xpath(
206
+ f"//span[@data-nb-field='{integer_field_name}']//span[@data-nb-value='1111' and contains(text(),'1111')]"
207
+ )
208
+ )
209
+ self.assertTrue(
210
+ self.browser.is_element_present_by_xpath(
211
+ f"//span[@data-nb-field='{select_field_name}']//span[@data-nb-value='SingleSelect Option A' and contains(text(),'SingleSelect Option A')]"
212
+ )
213
+ )
214
+ self.assertTrue(
215
+ self.browser.is_element_present_by_xpath(
216
+ f"//span[@data-nb-field='{select_field_name}']//span[@data-nb-value='SingleSelect Option B' and contains(text(),'SingleSelect Option B')]"
217
+ )
218
+ )
219
+ self.assertTrue(
220
+ self.browser.is_element_present_by_xpath(
221
+ f"//span[@data-nb-field='{multi_select_field_name}']//span[@data-nb-value='MultiSelect Option A' and contains(text(),'MultiSelect Option A')]"
222
+ )
223
+ )
224
+ self.assertTrue(
225
+ self.browser.is_element_present_by_xpath(
226
+ f"//span[@data-nb-field='{multi_select_field_name}']//span[@data-nb-value='MultiSelect Option B' and contains(text(),'MultiSelect Option B')]"
227
+ )
228
+ )
188
229
 
189
230
  # Assert on update of field in Advanced Filter the update is replicated on Default Filter
190
- self.change_field_value(text_field_name, "test new update", idx=2)
191
- self.change_field_value(integer_field_name, 8888, idx=2)
231
+ lookup_field_field_name = "form-0-lookup_field"
232
+ lookup_type_field_name = "form-0-lookup_type"
233
+ dynamic_filter_add_button = self.browser.find_by_xpath("//button[contains(@class, 'nb-dynamic-filter-add')]")
234
+ self.change_field_value(lookup_field_field_name, self.cf_text_field_label, field_type="select")
235
+ self.change_field_value(lookup_type_field_name, "contains", field_type="select")
236
+ self.change_field_value(text_field_name, "test new update", idx=1)
237
+ dynamic_filter_add_button.click()
238
+ self.change_field_value(lookup_field_field_name, self.cf_integer_field_label, field_type="select")
239
+ self.change_field_value(lookup_type_field_name, "exact", field_type="select")
240
+ self.change_field_value(integer_field_name, 8888, idx=1)
241
+ dynamic_filter_add_button.click()
242
+ self.change_field_value(lookup_field_field_name, self.cf_select_field_label, field_type="select")
243
+ self.change_field_value(lookup_type_field_name, "exact", field_type="select")
192
244
  self.change_field_value(
193
245
  select_field_name,
194
246
  "SingleSelect Option C",
195
247
  field_type="select",
196
248
  idx=1,
197
- select2_field_name="form-2-lookup_value",
249
+ select2_field_name=f"for_{select_field_name}",
198
250
  )
251
+ dynamic_filter_add_button.click()
252
+ self.change_field_value(lookup_field_field_name, self.cf_multi_select_field_label, field_type="select")
253
+ self.change_field_value(lookup_type_field_name, "contains", field_type="select")
199
254
  self.change_field_value(
200
255
  multi_select_field_name,
201
256
  "MultiSelect Option C",
202
257
  field_type="select",
203
258
  idx=1,
204
- select2_field_name="form-1-lookup_value",
259
+ select2_field_name=f"for_{multi_select_field_name}",
205
260
  )
261
+ dynamic_filter_add_button.click()
206
262
  self.browser.find_by_xpath("//a[@href='#default-filter']").click()
207
- self.assertEqual(self.browser.find_by_name(text_field_name)[1].value, "test new update")
208
- self.assertEqual(self.browser.find_by_name(integer_field_name)[1].value, "8888")
209
- custom_select_values = self.browser.find_by_name(select_field_name)[1].find_by_tag("option")
263
+ self.browser.execute_script(
264
+ f"document.querySelector('[name={text_field_name}]').scrollIntoView({{ behavior: 'instant', block: 'end' }})"
265
+ )
266
+ self.assertEqual(self.browser.find_by_name(text_field_name)[0].value, "test new update")
267
+ self.assertEqual(self.browser.find_by_name(integer_field_name)[0].value, "8888")
268
+ custom_select_values = self.browser.find_by_name(select_field_name)[0].find_by_tag("option")
210
269
  self.assertEqual(custom_select_values[0].value, "SingleSelect Option A")
211
270
  self.assertEqual(custom_select_values[1].value, "SingleSelect Option B")
212
271
  self.assertEqual(custom_select_values[2].value, "SingleSelect Option C")
213
- multi_custom_select_values = self.browser.find_by_name(multi_select_field_name)[1].find_by_tag("option")
272
+ multi_custom_select_values = self.browser.find_by_name(multi_select_field_name)[0].find_by_tag("option")
214
273
  self.assertEqual(multi_custom_select_values[0].value, "MultiSelect Option A")
215
274
  self.assertEqual(multi_custom_select_values[1].value, "MultiSelect Option B")
216
275
  self.assertEqual(multi_custom_select_values[2].value, "MultiSelect Option C")
217
276
 
218
277
  # Assert on update of filter, the new filter is applied
219
278
  self.browser.find_by_xpath(apply_btn_xpath).click() # Click on apply filter button
279
+ self.browser.find_by_xpath(
280
+ "//a[@href='#advanced-filter']"
281
+ ).click() # Go to Advanced tab to view applied filters badges
220
282
  self.assertTrue(self.browser.is_text_present("test new update"))
221
283
  self.assertTrue(self.browser.is_text_present("8888"))
222
284
  self.assertTrue(self.browser.is_text_present("SingleSelect Option C"))
@@ -227,10 +289,10 @@ class ListViewFilterTestCase(SeleniumTestCase):
227
289
  # Go to the location list view
228
290
  self.browser.visit(f"{self.live_server_url}{reverse('dcim:location_list')}")
229
291
  # create a new tag
230
- tag = Tag.objects.create(name="Tag1")
231
- tag.content_types.set([ContentType.objects.get_for_model(Location)])
292
+ tag_object = Tag.objects.create(name="Tag1")
293
+ tag_object.content_types.set([ContentType.objects.get_for_model(Location)])
232
294
 
233
- # Open the filter modal
295
+ # Open the filter drawer
234
296
  self.browser.find_by_id("id__filterbtn").click()
235
297
  # Go to advanced Tab
236
298
  self.browser.find_by_xpath("//a[@href='#advanced-filter']").click()
@@ -250,19 +312,25 @@ class ListViewFilterTestCase(SeleniumTestCase):
250
312
  ).click()
251
313
  # find the input field for the tag
252
314
  container = self.browser.find_by_xpath(
253
- "//span[@class='select2 select2-container select2-container--bootstrap select2-container--below']"
315
+ "//span[@class='select2 select2-container select2-container--bootstrap-5 select2-container--below']"
254
316
  )
255
317
  container.click()
256
318
  # select tag
257
319
  self.browser.find_by_xpath(
258
320
  "//span[@class='select2-results']//ul[@class='select2-results__options']/li[contains(@class,'select2-results__option') "
259
- f"and contains(text(),{tag.name})]"
321
+ f"and contains(text(),{tag_object.name})]"
260
322
  ).click()
261
323
 
262
324
  apply_btn_xpath = "//div[@id='advanced-filter']//button[@type='submit']"
263
325
  self.browser.find_by_xpath(apply_btn_xpath).click()
264
- filter_modal = self.browser.find_by_id("FilterForm_modal", wait_time=10)
265
- # Model disappears
266
- self.assertFalse(filter_modal.visible)
326
+ filter_drawer = self.browser.find_by_id("FilterForm_drawer", wait_time=10)
327
+ # Drawer is kept open
328
+ self.assertTrue(filter_drawer.visible)
267
329
  # Assert the choice is applied
268
- self.browser.find_by_xpath(f"//li[@class='filter-selection-choice' and contains(text(),{tag.name})]")
330
+ self.browser.find_by_xpath(
331
+ f"//span[@class='badge' and @data-nb-value='{tag_object.name}' and contains(text(),{tag_object.name})]"
332
+ )
333
+ # Assert that proper Advanced tab badge is displayed
334
+ self.browser.find_by_xpath(
335
+ "//a[@href='#advanced-filter']//span[contains(@class,'nb-btn-indicator') and contains(text(),'Some of the applied filters can only be viewed in Advanced')]"
336
+ )
@@ -32,5 +32,5 @@ class StaticMediaFailureTestCase(SeleniumTestCase):
32
32
  # Wait for body element to appear
33
33
  self.assertTrue(self.browser.is_element_present_by_tag("body", wait_time=10), "Page failed to load")
34
34
  # Ensure we weren't redirected to another page
35
- self.assertEqual(self.browser.url, self.live_server_url + url)
35
+ self.assertTrue(self.browser.url.startswith(self.live_server_url + url))
36
36
  self.assertTrue(self.browser.is_text_not_present("Static Media Failure"))
@@ -25,10 +25,7 @@ class HomeTestCase(SeleniumTestCase):
25
25
  def setUp(self):
26
26
  super().setUp()
27
27
  self.login(self.user.username, self.password)
28
-
29
- def tearDown(self):
30
- self.logout()
31
- super().tearDown()
28
+ self.logged_in = True
32
29
 
33
30
  def get_panel_permissions(self, panel_details):
34
31
  permissions = []
@@ -36,13 +33,6 @@ class HomeTestCase(SeleniumTestCase):
36
33
  permissions.append(panel["permission"])
37
34
  return permissions
38
35
 
39
- def test_login(self):
40
- """
41
- Perform a UI login.
42
- """
43
- # Wait for the page to render and make sure we got a body.
44
- self.browser.visit(self.live_server_url)
45
-
46
36
  def test_homepage_render(self):
47
37
  """
48
38
  Render homepage with app defined objects.
@@ -53,7 +43,7 @@ class HomeTestCase(SeleniumTestCase):
53
43
 
54
44
  self.browser.visit(self.live_server_url)
55
45
 
56
- columns_html = self.browser.find_by_css("div[class='homepage_column']")
46
+ columns_html = self.browser.find_by_xpath("//div[@id='draggable-homepage-panels']")
57
47
  for panel_name, panel_details in self.layout.items():
58
48
  columns_html.first.find_by_xpath(f".//strong[text()='{panel_name}']")
59
49
  for item_name, _ in panel_details.items():
@@ -69,7 +59,7 @@ class HomeTestCase(SeleniumTestCase):
69
59
 
70
60
  self.browser.visit(self.live_server_url)
71
61
 
72
- columns_html = self.browser.find_by_css("div[class='homepage_column']")
62
+ columns_html = self.browser.find_by_xpath("//div[@id='draggable-homepage-panels']")
73
63
  for panel_name, panel_details in self.layout.items():
74
64
  columns_html.first.find_by_xpath(f".//strong[text()='{panel_name}']")
75
65
  for item_name, item_details in panel_details.items():
@@ -95,12 +85,12 @@ class HomeTestCase(SeleniumTestCase):
95
85
  if any(perm in self.get_panel_permissions(panel_details) for perm in user_permissions):
96
86
  for item_name, item_details in panel_details.items():
97
87
  panel_element_to_search = self.browser.find_by_xpath(
98
- f".//div[@class='homepage_column']"
99
- f"/div[@class='panel panel-default']"
100
- f"/div[@class='panel-heading']"
88
+ f"//div[@id='draggable-homepage-panels']"
89
+ f"/div[@class='card nb-draggable']"
90
+ f"/div[@class='card-header nb-draggable-handle']"
101
91
  f"/strong[contains(text(), '{panel_name}')]"
102
92
  f"/../.."
103
- f"/div[@class='list-group collapse in collapsible-div']"
93
+ f"/ul[@class='list-group collapse overflow-y-auto show']"
104
94
  )
105
95
  links = panel_element_to_search.links.find_by_text(item_name)
106
96
  if item_details["permission"] in user_permissions:
@@ -108,5 +98,7 @@ class HomeTestCase(SeleniumTestCase):
108
98
  else:
109
99
  self.assertEqual(len(links), 0)
110
100
  else:
111
- panel = self.browser.find_by_xpath(f".//div[@class='panel-heading']/strong[text()='{panel_name}']")
101
+ panel = self.browser.find_by_xpath(
102
+ f"//div[@class='card-header nb-draggable-handle']/strong[text()='{panel_name}']"
103
+ )
112
104
  self.assertEqual(len(panel), 0)
@@ -6,18 +6,11 @@ class ImportObjectsUITestCase(SeleniumTestCase):
6
6
 
7
7
  def setUp(self):
8
8
  super().setUp()
9
- self.user.is_superuser = True
10
- self.user.save()
11
- self.login(self.user.username, self.password)
12
-
13
- def tearDown(self):
14
- self.logout()
15
- super().tearDown()
9
+ self.login_as_superuser()
16
10
 
17
11
  def test_import_objects_ui_population(self):
18
12
  self.browser.visit(self.live_server_url)
19
- self.browser.links.find_by_partial_text("Organization").click()
20
- self.browser.links.find_by_partial_text("Locations").click()
13
+ self.click_navbar_entry("Organization", "Locations")
21
14
  self.browser.find_by_id("actions-dropdown").click()
22
15
  self.browser.find_by_id("import-button").click()
23
16
 
@@ -1,4 +1,6 @@
1
+ from nautobot.core.apps import NavMenuGroup, NavMenuTab, register_menu_items
1
2
  from nautobot.core.testing.integration import SeleniumTestCase
3
+ from nautobot.extras.registry import registry
2
4
 
3
5
 
4
6
  class NavBarTestCase(SeleniumTestCase):
@@ -16,7 +18,7 @@ class NavBarTestCase(SeleniumTestCase):
16
18
  "buttons": ["Add"],
17
19
  },
18
20
  },
19
- "Tags": {
21
+ "Metadata": {
20
22
  "Tags": {
21
23
  "permission": "extras.view_tag",
22
24
  "buttons": ["Add"],
@@ -24,7 +26,7 @@ class NavBarTestCase(SeleniumTestCase):
24
26
  },
25
27
  },
26
28
  "Extensibility": {
27
- "Data Management": {
29
+ "Data Model": {
28
30
  "Relationships": {
29
31
  "permission": "extras.view_relationship",
30
32
  "buttons": [
@@ -39,10 +41,6 @@ class NavBarTestCase(SeleniumTestCase):
39
41
  super().setUp()
40
42
  self.login(self.user.username, self.password)
41
43
 
42
- def tearDown(self):
43
- self.logout()
44
- super().tearDown()
45
-
46
44
  def test_navbar_render_superuser(self):
47
45
  """
48
46
  Render navbar from home page with superuser.
@@ -56,20 +54,22 @@ class NavBarTestCase(SeleniumTestCase):
56
54
 
57
55
  for tab_name, groups in self.navbar.items():
58
56
  # XPath to find tabs using the tab name
59
- tab_xpath = f"//*[@id='navbar']//span[normalize-space()='{tab_name}']/.."
60
- tab = self.browser.find_by_xpath(tab_xpath)
61
- tab.click()
62
- self.assertEqual(tab["aria-expanded"], "true")
57
+ sidenav_section = self.find_sidenav_section(tab_name)
58
+ sidenav_section.button.click()
59
+
60
+ self.assertTrue(sidenav_section.is_expanded)
63
61
 
64
62
  for group_name, items in groups.items():
65
63
  # Append onto tab xpath with group name search
66
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='{group_name}']")
64
+ group = sidenav_section.flyout.find_by_xpath(
65
+ f"//li[@class='nb-sidenav-link-group' and normalize-space()='{group_name}']"
66
+ )
67
67
 
68
68
  for item_name in items:
69
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='{item_name}']]"
69
+ item_xpath = f"//a[@class='nb-sidenav-link' and normalize-space()='{item_name}']"
70
70
  group.find_by_xpath(item_xpath)
71
71
 
72
- tab.click()
72
+ sidenav_section.button.click()
73
73
 
74
74
  def test_navbar_render_with_limited_permissions(self):
75
75
  """
@@ -91,8 +91,33 @@ class NavBarTestCase(SeleniumTestCase):
91
91
  tab_flag = True
92
92
 
93
93
  # XPath to find tabs using the tab name
94
- tabs = self.browser.find_by_xpath(f"//*[@id='navbar']//span[normalize-space()='{tab_name}']/..")
94
+ tabs = self.browser.find_by_xpath(f"//*[@id='sidenav']//li[@data-section-name='{tab_name}']")
95
95
  if tab_flag:
96
- self.assertEqual(len(tabs), 1)
96
+ self.assertEqual(len(tabs), 1, msg=f'"{tab_name}" was unexpectedly not found.')
97
97
  else:
98
- self.assertEqual(len(tabs), 0)
98
+ self.assertTrue(tabs.is_empty(), msg=f'"{tab_name}" was unexpectedly found.')
99
+
100
+ def test_navbar_render_with_missing_items_and_no_empty_tabs_and_groups(self):
101
+ """
102
+ Render navbar from home page with missing items.
103
+ This could potentially lead to displaying empty tabs and groups on the navbar.
104
+ Check the UI and assert that tabs and groups with no items are not rendered.
105
+ """
106
+
107
+ register_menu_items((NavMenuTab(name="Test Tab", groups=(NavMenuGroup(name="Test Group", items=()),)),))
108
+
109
+ try:
110
+ self.user.is_superuser = True
111
+ self.user.save()
112
+
113
+ self.browser.visit(self.live_server_url)
114
+
115
+ group = self.browser.find_by_xpath(
116
+ "//*[@id='sidenav']//li[@data-group-weight and normalize-space()='Test Group']"
117
+ )
118
+ self.assertEqual(len(group), 0)
119
+
120
+ tab = self.browser.find_by_xpath("//*[@id='sidenav']//li[@data-section-name='Test Tab']")
121
+ self.assertEqual(len(tab), 0)
122
+ finally:
123
+ del registry["nav_menu"]["tabs"]["Test Tab"]
@@ -8,13 +8,7 @@ class SwaggerUITestCase(SeleniumTestCase):
8
8
 
9
9
  def setUp(self):
10
10
  super().setUp()
11
- self.user.is_superuser = True
12
- self.user.save()
13
- self.login(self.user.username, self.password)
14
-
15
- def tearDown(self):
16
- self.logout()
17
- super().tearDown()
11
+ self.login_as_superuser()
18
12
 
19
13
  def test_endpoint_render(self):
20
14
  """Check that the dcim.location API endpoints are rendered correctly."""
@@ -1,3 +1,5 @@
1
+ from django.test import tag
2
+
1
3
  from nautobot.core.testing.integration import SeleniumTestCase
2
4
 
3
5
 
@@ -22,6 +24,7 @@ class ThemeTestCase(SeleniumTestCase):
22
24
  # Validate modal is not visible
23
25
  self.assertFalse(theme_modal[0].visible)
24
26
 
27
+ @tag("fix_in_v3")
25
28
  def test_modal_rendered(self):
26
29
  """Modal should render when selecting the 'theme' button in the footer."""
27
30
 
@@ -0,0 +1,4 @@
1
+ from nautobot.core.tests.nautobot_config import * # noqa: F403
2
+
3
+ # Do not enable example apps
4
+ PLUGINS = []
@@ -39,11 +39,12 @@ class NautobotParallelTestSuite(ParallelTestSuite):
39
39
 
40
40
  class NautobotTestRunner(DiscoverRunner):
41
41
  """
42
- Custom test runner that excludes (slow) integration and migration tests by default.
42
+ Custom test runner that excludes (slow) integration and migration tests by default among others.
43
43
 
44
44
  This test runner is aware of our use of the "integration" tag and only runs integration tests if
45
45
  explicitly passed in with `nautobot-server test --tag integration`.
46
46
  Similarly, it only runs migration tests if explicitly called with `--tag migration_test`.
47
+ Similarly, it only runs tests that require the example app(s) if those are present in settings.PLUGINS.
47
48
 
48
49
  By Nautobot convention, integration tests must be tagged with "integration". The base
49
50
  `nautobot.core.testing.integration.SeleniumTestCase` has this tag, therefore any test cases
@@ -58,6 +59,10 @@ class NautobotTestRunner(DiscoverRunner):
58
59
  parallel_test_suite = NautobotParallelTestSuite
59
60
 
60
61
  exclude_tags = ["integration", "migration_test"]
62
+ if "example_app" not in settings.PLUGINS:
63
+ exclude_tags.append("example_app")
64
+ if "example_app_with_view_override" not in settings.PLUGINS:
65
+ exclude_tags.append("example_app_with_view_override")
61
66
 
62
67
  @classmethod
63
68
  def add_arguments(cls, parser):
@@ -533,10 +533,12 @@ class ModelViewSetMixinTest(testing.APITestCase):
533
533
  self.user.is_superuser = True
534
534
  self.user.save()
535
535
 
536
- # Default behavior - m2m fields included
536
+ # With exclude_m2m query parameter set to False
537
537
  view = self.SimpleIPAddressViewSet()
538
538
  view.action_map = {"get": "list"}
539
- request = APIRequestFactory().get(reverse("ipam-api:ipaddress-list"), headers=self.header)
539
+ request = APIRequestFactory().get(
540
+ reverse("ipam-api:ipaddress-list"), headers=self.header, data={"exclude_m2m": False}
541
+ )
540
542
  force_authenticate(request, user=self.user)
541
543
  request = view.initialize_request(request)
542
544
  view.setup(request)
@@ -561,7 +563,7 @@ class ModelViewSetMixinTest(testing.APITestCase):
561
563
  list(instance.vm_interfaces.all())
562
564
  list(instance.tags.all())
563
565
 
564
- # With exclude_m2m query parameter
566
+ # With exclude_m2m query parameter set to True
565
567
  view = self.SimpleIPAddressViewSet()
566
568
  view.action_map = {"get": "list"}
567
569
  request = APIRequestFactory().get(