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
@@ -6,7 +6,7 @@ from django import forms as django_forms
6
6
  from django.apps import apps as django_apps
7
7
  from django.contrib.contenttypes.models import ContentType
8
8
  from django.http import QueryDict
9
- from django.test import TestCase
9
+ from django.test import tag
10
10
  from django.urls import reverse
11
11
  from django_filters.filterset import FilterSet
12
12
  from netaddr import IPNetwork
@@ -20,19 +20,7 @@ from nautobot.extras import filters as extras_filters, models as extras_models
20
20
  from nautobot.ipam import forms as ipam_forms, models as ipam_models
21
21
 
22
22
 
23
- class SearchFormTestCase(TestCase):
24
- def test_q_placeholder(self):
25
- from nautobot.core.forms import SearchForm
26
-
27
- self.assertEqual(SearchForm().fields["q"].widget.attrs["placeholder"], "Search")
28
-
29
- # Assert the q field placeholder is overridden
30
- self.assertEqual(
31
- SearchForm(q_placeholder="Search Locations").fields["q"].widget.attrs["placeholder"], "Search Locations"
32
- )
33
-
34
-
35
- class ExpandIPAddress(TestCase):
23
+ class ExpandIPAddress(testing.TestCase):
36
24
  """
37
25
  Validate the operation of expand_ipaddress_pattern().
38
26
  """
@@ -215,7 +203,7 @@ class ExpandIPAddress(TestCase):
215
203
  sorted(forms.expand_ipaddress_pattern("1.2.3.[4,,5]/32", 4))
216
204
 
217
205
 
218
- class ExpandAlphanumeric(TestCase):
206
+ class ExpandAlphanumeric(testing.TestCase):
219
207
  """
220
208
  Validate the operation of expand_alphanumeric_pattern().
221
209
  """
@@ -349,7 +337,7 @@ class ExpandAlphanumeric(TestCase):
349
337
  sorted(forms.expand_alphanumeric_pattern("r[a,,b]a"))
350
338
 
351
339
 
352
- class AddFieldToFormClassTest(TestCase):
340
+ class AddFieldToFormClassTest(testing.TestCase):
353
341
  def test_field_added(self):
354
342
  """
355
343
  Test adding of a new field to an existing form.
@@ -378,7 +366,7 @@ class AddFieldToFormClassTest(TestCase):
378
366
  )
379
367
 
380
368
 
381
- class DynamicModelChoiceFieldTest(TestCase):
369
+ class DynamicModelChoiceFieldTest(testing.TestCase):
382
370
  """Tests for DynamicModelChoiceField."""
383
371
 
384
372
  def setUp(self):
@@ -414,7 +402,7 @@ class DynamicModelChoiceFieldTest(TestCase):
414
402
  self.assertEqual(self.field_with_to_field_name.prepare_value(address), address.address)
415
403
 
416
404
 
417
- class DynamicModelMultipleChoiceFieldTest(TestCase):
405
+ class DynamicModelMultipleChoiceFieldTest(testing.TestCase):
418
406
  """Tests for DynamicModelMultipleChoiceField."""
419
407
 
420
408
  def setUp(self):
@@ -451,7 +439,7 @@ class DynamicModelMultipleChoiceFieldTest(TestCase):
451
439
  )
452
440
 
453
441
 
454
- class MultiValueCharFieldTest(TestCase):
442
+ class MultiValueCharFieldTest(testing.TestCase):
455
443
  def setUp(self):
456
444
  self.filter = filters.MultiValueCharFilter()
457
445
  self.field = forms.MultiValueCharField()
@@ -484,7 +472,7 @@ class MultiValueCharFieldTest(TestCase):
484
472
  )
485
473
 
486
474
 
487
- class NumericArrayFieldTest(TestCase):
475
+ class NumericArrayFieldTest(testing.TestCase):
488
476
  def setUp(self):
489
477
  super().setUp()
490
478
  # We need to use a field with required=False so we can test empty/None inputs
@@ -514,7 +502,7 @@ class NumericArrayFieldTest(TestCase):
514
502
  self.field.clean(test)
515
503
 
516
504
 
517
- class AddressFieldMixinTest(TestCase):
505
+ class AddressFieldMixinTest(testing.TestCase):
518
506
  """Test cases for the AddressFieldMixin."""
519
507
 
520
508
  def setUp(self):
@@ -544,7 +532,7 @@ class AddressFieldMixinTest(TestCase):
544
532
  mock_init.assert_called_with(initial=self.initial, instance=self.ip)
545
533
 
546
534
 
547
- class PrefixFieldMixinTest(TestCase):
535
+ class PrefixFieldMixinTest(testing.TestCase):
548
536
  """Test cases for the PrefixFieldMixin."""
549
537
 
550
538
  def setUp(self):
@@ -593,7 +581,7 @@ class JSONFieldTest(testing.TestCase):
593
581
  self.assertEqual('"I am UTF-8! 😀"', forms.JSONField().prepare_value("I am UTF-8! 😀"))
594
582
 
595
583
 
596
- class MultiMatchModelMultipleChoiceFieldTest(TestCase):
584
+ class MultiMatchModelMultipleChoiceFieldTest(testing.TestCase):
597
585
  def test_clean(self):
598
586
  field = forms.MultiMatchModelMultipleChoiceField(
599
587
  queryset=ipam_models.VLANGroup.objects.all(), to_field_name="name"
@@ -621,14 +609,14 @@ class MultiMatchModelMultipleChoiceFieldTest(TestCase):
621
609
  field.clean(value)
622
610
 
623
611
 
624
- class WidgetsTest(TestCase):
612
+ class WidgetsTest(testing.TestCase):
625
613
  def test_api_select_add_query_param_with_utf8(self):
626
614
  widget = forms.APISelect()
627
615
  widget.add_query_param("utf8", "I am UTF-8! 😀")
628
616
  self.assertEqual('["I am UTF-8! 😀"]', widget.attrs["data-query-param-utf8"])
629
617
 
630
618
 
631
- class DynamicFilterFormTest(TestCase):
619
+ class DynamicFilterFormTest(testing.TestCase):
632
620
  def test_get_filterset_parameter_form_field_all_filters(self):
633
621
  """
634
622
  Test every FilterSet to validate that Plural names are correctly mapped in get_filterset_parameter_form_field.
@@ -671,6 +659,7 @@ class DynamicFilterFormTest(TestCase):
671
659
  # DynamicFilterForm()
672
660
  # self.assertEqual("'DynamicFilterForm' object requires `filterset_class` attribute", str(err.exception))
673
661
 
662
+ @tag("example_app")
674
663
  def test_dynamic_filter_form(self):
675
664
  form = forms.DynamicFilterForm(filterset=extras_filters.StatusFilterSet())
676
665
  location_form = forms.DynamicFilterForm(filterset=dcim_filters.LocationFilterSet())
@@ -678,7 +667,6 @@ class DynamicFilterFormTest(TestCase):
678
667
 
679
668
  with self.subTest("Assert get_lookup_field_choices"):
680
669
  self.assertEqual(
681
- form._get_lookup_field_choices(),
682
670
  [
683
671
  ("color", "Color"),
684
672
  ("contacts", "Contacts (name or ID)"),
@@ -690,13 +678,13 @@ class DynamicFilterFormTest(TestCase):
690
678
  ("name", "Name"),
691
679
  ("teams", "Teams (name or ID)"),
692
680
  ],
681
+ form._get_lookup_field_choices(),
693
682
  )
694
683
  self.assertEqual(
695
- location_form._get_lookup_field_choices(),
696
684
  [
697
685
  ("asn", "ASN"),
698
686
  ("child_location_type", "Child location type (name or ID)"),
699
- ("circuit_terminations", "Circuit terminations"),
687
+ ("circuit_terminations", "Circuit terminations (ID)"),
700
688
  ("clusters", "Clusters (name or ID)"),
701
689
  ("comments", "Comments"),
702
690
  ("contact_email", "Contact E-mail"),
@@ -729,9 +717,9 @@ class DynamicFilterFormTest(TestCase):
729
717
  ("parent", "Parent location (name or ID)"),
730
718
  ("physical_address", "Physical address"),
731
719
  ("power_panels", "Power panels (name or ID)"),
732
- ("prefixes", "Prefixes"),
733
- ("racks", "Rack (name or ID)"),
720
+ ("prefixes", "Prefixes (ID)"),
734
721
  ("rack_groups", "Rack groups (name or ID)"),
722
+ ("racks", "Racks (name or ID)"),
735
723
  ("shipping_address", "Shipping address"),
736
724
  ("status", "Status (name or ID)"),
737
725
  ("vlans", "Tagged VLANs (VID or ID)"),
@@ -743,6 +731,7 @@ class DynamicFilterFormTest(TestCase):
743
731
  ("time_zone", "Time zone"),
744
732
  ("vlan_groups", "VLAN groups (name or ID)"),
745
733
  ],
734
+ location_form._get_lookup_field_choices(),
746
735
  )
747
736
 
748
737
  with self.subTest(
@@ -17,8 +17,7 @@ from django.urls import reverse
17
17
  import graphene.types
18
18
  from graphene_django.registry import get_global_registry
19
19
  from graphene_django.settings import graphene_settings
20
- from graphql import get_default_backend, GraphQLError
21
- from graphql.error.located_error import GraphQLLocatedError
20
+ from graphql import execute, get_introspection_query, graphql_sync, GraphQLError, parse
22
21
  import redis.exceptions
23
22
  from rest_framework import status
24
23
 
@@ -39,6 +38,7 @@ from nautobot.core.graphql.schema import (
39
38
  from nautobot.core.graphql.types import DateType, OptimizedNautobotObjectType
40
39
  from nautobot.core.graphql.utils import str_to_var_name
41
40
  from nautobot.core.testing import create_test_user, NautobotTestClient, TestCase
41
+ from nautobot.core.utils.cache import construct_cache_key
42
42
  from nautobot.dcim.choices import ConsolePortTypeChoices, InterfaceModeChoices, InterfaceTypeChoices, PortTypeChoices
43
43
  from nautobot.dcim.filters import DeviceFilterSet, LocationFilterSet
44
44
  from nautobot.dcim.graphql.types import DeviceType as DeviceTypeGraphQL
@@ -99,7 +99,9 @@ User = get_user_model()
99
99
 
100
100
  class GraphQLTestCaseBase(TestCase):
101
101
  def setUp(self):
102
- self.SCHEMA = graphene_settings.SCHEMA # not a no-op; this causes the schema to be built when first called
102
+ self.SCHEMA = (
103
+ graphene_settings.SCHEMA.graphql_schema
104
+ ) # not a no-op; this causes the schema to be built when first called
103
105
 
104
106
 
105
107
  class GraphQLTestCase(GraphQLTestCaseBase):
@@ -109,7 +111,7 @@ class GraphQLTestCase(GraphQLTestCaseBase):
109
111
  GraphQLQuery.objects.create(name="GQL 1", query="{ query: locations {name} }")
110
112
  GraphQLQuery.objects.create(name="GQL 2", query="query ($name: [String!]) { locations(name:$name) {name} }")
111
113
  self.location_type = LocationType.objects.get(name="Campus")
112
- location_status = Status.objects.get_for_model(Location).first()
114
+ location_status = Status.objects.get_for_model(Location)[0]
113
115
  self.locations = (
114
116
  Location.objects.create(name="Location-1", location_type=self.location_type, status=location_status),
115
117
  Location.objects.create(name="Location-2", location_type=self.location_type, status=location_status),
@@ -119,9 +121,9 @@ class GraphQLTestCase(GraphQLTestCaseBase):
119
121
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
120
122
  def test_execute_query(self):
121
123
  query = "{ query: locations {name} }"
122
- resp = execute_query(query, user=self.user).to_dict()
123
- self.assertFalse(resp["data"].get("error"))
124
- self.assertEqual(len(resp["data"]["query"]), Location.objects.all().count())
124
+ resp = execute_query(query, user=self.user)
125
+ self.assertIsNone(resp.errors)
126
+ self.assertEqual(len(resp.data["query"]), Location.objects.all().count())
125
127
 
126
128
  @skip("Works in isolation, fails as part of the overall test suite due to issue #446")
127
129
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -136,8 +138,8 @@ class GraphQLTestCase(GraphQLTestCaseBase):
136
138
  self.locations[0]._custom_field_data = custom_field_data
137
139
  self.locations[0].save()
138
140
  query = "query ($name: [String!]) { locations(name:$name) {name, _custom_field_data, cf_custom_date_field} }"
139
- resp = execute_query(query, user=self.user, variables={"name": "Location-1"}).to_dict()
140
- self.assertEqual(resp["data"]["locations"]["cf_custom_date_field"], custom_field_data)
141
+ resp = execute_query(query, user=self.user, variables={"name": "Location-1"})
142
+ self.assertEqual(resp.data["locations"]["cf_custom_date_field"], custom_field_data)
141
143
 
142
144
  @skip("Works in isolation, fails as part of the overall test suite due to issue #446")
143
145
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -152,28 +154,28 @@ class GraphQLTestCase(GraphQLTestCaseBase):
152
154
  self.locations[0]._custom_field_data = custom_field_data
153
155
  self.locations[0].save()
154
156
  query = "query ($name: [String!]) { locations(name:$name) {name, _custom_field_data, cf_custom_json_field} }"
155
- resp = execute_query(query, user=self.user, variables={"name": "Location-1"}).to_dict()
156
- self.assertEqual(resp["data"]["locations"]["cf_custom_json_field"], custom_field_data)
157
+ resp = execute_query(query, user=self.user, variables={"name": "Location-1"})
158
+ self.assertEqual(resp.data["locations"]["cf_custom_json_field"], custom_field_data)
157
159
 
158
160
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
159
161
  def test_execute_query_with_variable(self):
160
162
  query = "query ($name: [String!]) { locations(name:$name) {name} }"
161
- resp = execute_query(query, user=self.user, variables={"name": "Location-1"}).to_dict()
162
- self.assertFalse(resp.get("error"))
163
- self.assertEqual(len(resp["data"]["locations"]), 1)
163
+ resp = execute_query(query, user=self.user, variables={"name": "Location-1"})
164
+ self.assertIsNone(resp.errors)
165
+ self.assertEqual(len(resp.data["locations"]), 1)
164
166
 
165
167
  def test_execute_query_with_error(self):
166
168
  query = "THIS TEST WILL ERROR"
167
169
  with self.assertRaises(GraphQLError):
168
- execute_query(query, user=self.user).to_dict()
170
+ execute_query(query, user=self.user)
169
171
 
170
172
  def test_execute_saved_query(self):
171
- resp = execute_saved_query("GQL 1", user=self.user).to_dict()
172
- self.assertFalse(resp["data"].get("error"))
173
+ resp = execute_saved_query("GQL 1", user=self.user)
174
+ self.assertIsNone(resp.errors)
173
175
 
174
176
  def test_execute_saved_query_with_variable(self):
175
- resp = execute_saved_query("GQL 2", user=self.user, variables={"name": "location-1"}).to_dict()
176
- self.assertFalse(resp["data"].get("error"))
177
+ resp = execute_saved_query("GQL 2", user=self.user, variables={"name": "location-1"})
178
+ self.assertIsNone(resp.errors)
177
179
 
178
180
  def test_graphql_types_registry(self):
179
181
  """Ensure models with graphql feature are registered in the graphene_django registry."""
@@ -186,8 +188,8 @@ class GraphQLTestCase(GraphQLTestCaseBase):
186
188
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
187
189
  def test_graphql_url_field(self):
188
190
  """Test the url field for all graphql types."""
189
- schema = self.SCHEMA.introspect()
190
- graphql_fields = schema["__schema"]["types"][0]["fields"]
191
+ introspection_query_result = graphql_sync(self.SCHEMA, get_introspection_query(descriptions=False))
192
+ graphql_fields = introspection_query_result.data["__schema"]["types"][0]["fields"]
191
193
  for graphql_field in graphql_fields:
192
194
  if graphql_field["type"]["kind"] == "LIST" or graphql_field["name"] == "content_type":
193
195
  continue
@@ -208,12 +210,12 @@ class GraphQLTestCase(GraphQLTestCaseBase):
208
210
  query = f'{{ query: {graphql_field["name"]}(id:"{obj.pk}") {{ url }} }}'
209
211
  request = RequestFactory(SERVER_NAME="nautobot.example.com").post("/graphql/")
210
212
  request.user = self.user
211
- resp = execute_query(query, request=request).to_dict()
213
+ resp = execute_query(query, request=request)
212
214
  self.assertIsNotNone(
213
- resp["data"]["query"]["url"], f"No url returned in graphql for {graphql_field['name']}"
215
+ resp.data["query"]["url"], f"No url returned in graphql for {graphql_field['name']}"
214
216
  )
215
217
  self.assertTrue(
216
- resp["data"]["query"]["url"].endswith(f"/{obj.pk}/"),
218
+ resp.data["query"]["url"].endswith(f"/{obj.pk}/"),
217
219
  f"Mismatched url returned in graphql for {graphql_field['name']}",
218
220
  )
219
221
 
@@ -228,13 +230,13 @@ class GraphQLUtilsTestCase(GraphQLTestCaseBase):
228
230
  class GraphQLGenerateSchemaTypeTestCase(GraphQLTestCaseBase):
229
231
  def test_model_w_filterset(self):
230
232
  schema = generate_schema_type(app_name="dcim", model=Device)
231
- self.assertEqual(schema.__bases__[0], OptimizedNautobotObjectType)
233
+ self.assertIn(OptimizedNautobotObjectType, schema.__bases__)
232
234
  self.assertEqual(schema._meta.model, Device)
233
235
  self.assertEqual(schema._meta.filterset_class, DeviceFilterSet)
234
236
 
235
237
  def test_model_wo_filterset(self):
236
238
  schema = generate_schema_type(app_name="wrong_app", model=ChangeLoggedModel)
237
- self.assertEqual(schema.__bases__[0], OptimizedNautobotObjectType)
239
+ self.assertIn(OptimizedNautobotObjectType, schema.__bases__)
238
240
  self.assertEqual(schema._meta.model, ChangeLoggedModel)
239
241
  self.assertIsNone(schema._meta.filterset_class)
240
242
 
@@ -402,8 +404,12 @@ class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
402
404
  def tearDown(self):
403
405
  """Ensure that relationship caches are cleared to avoid leakage into other tests."""
404
406
  with contextlib.suppress(redis.exceptions.ConnectionError):
405
- cache.delete_pattern(f"{Relationship.objects.get_for_model_source.cache_key_prefix}.*")
406
- cache.delete_pattern(f"{Relationship.objects.get_for_model_destination.cache_key_prefix}.*")
407
+ cache.delete_pattern(
408
+ f"{construct_cache_key(Relationship.objects, method_name='get_for_model_source', branch_aware=True)}(*)"
409
+ )
410
+ cache.delete_pattern(
411
+ f"{construct_cache_key(Relationship.objects, method_name='get_for_model_destination', branch_aware=True)}(*)"
412
+ )
407
413
 
408
414
  def test_extend_relationship_default_prefix(self):
409
415
  """Verify that relationships are correctly added to the schema."""
@@ -472,14 +478,15 @@ class GraphQLSearchParameters(GraphQLTestCaseBase):
472
478
  def test_search_parameters(self):
473
479
  fields = LocationFilterSet().filters.keys()
474
480
  params = generate_list_search_parameters(self.schema)
475
- exclude_filters = ["type"]
481
+ args_filters = ["description"]
476
482
 
477
483
  for field in fields:
478
484
  field = str_to_var_name(field)
479
- if field not in exclude_filters:
480
- self.assertIn(field, params.keys())
481
- else:
485
+ if field in args_filters:
482
486
  self.assertNotIn(field, params.keys())
487
+ self.assertIn(field, params["args"].keys())
488
+ else:
489
+ self.assertIn(field, params.keys())
483
490
 
484
491
 
485
492
  class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
@@ -514,7 +521,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
514
521
  cls.clients[3].credentials(HTTP_AUTHORIZATION=f"Token {cls.tokens[3].key}")
515
522
 
516
523
  cls.location_type = LocationType.objects.get(name="Campus")
517
- location_status = Status.objects.get_for_model(Location).first()
524
+ location_status = Status.objects.get_for_model(Location)[0]
518
525
  cls.locations = (
519
526
  Location.objects.create(name="Location 1", location_type=cls.location_type, status=location_status),
520
527
  Location.objects.create(name="Location 2", location_type=cls.location_type, status=location_status),
@@ -544,7 +551,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
544
551
  location_obj_permission.groups.add(cls.groups[i])
545
552
  location_obj_permission.users.add(cls.users[i])
546
553
 
547
- rack_status = Status.objects.get_for_model(Rack).first()
554
+ rack_status = Status.objects.get_for_model(Rack)[0]
548
555
  cls.rack_grp1 = (
549
556
  Rack.objects.create(name="Rack 1-1", location=cls.locations[0], status=rack_status),
550
557
  Rack.objects.create(name="Rack 1-2", location=cls.locations[0], status=rack_status),
@@ -603,14 +610,14 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
603
610
  """Validate that users can query based on their permissions."""
604
611
  # First user
605
612
  response = self.clients[0].post(self.api_url, {"query": self.get_racks_query}, format="json")
606
- self.assertEqual(response.status_code, status.HTTP_200_OK)
613
+ self.assertHttpStatus(response, status.HTTP_200_OK)
607
614
  self.assertIsInstance(response.data["data"]["racks"], list)
608
615
  names = [item["name"] for item in response.data["data"]["racks"]]
609
616
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2"])
610
617
 
611
618
  # Second user
612
619
  response = self.clients[1].post(self.api_url, {"query": self.get_racks_query}, format="json")
613
- self.assertEqual(response.status_code, status.HTTP_200_OK)
620
+ self.assertHttpStatus(response, status.HTTP_200_OK)
614
621
  self.assertIsInstance(response.data["data"]["racks"], list)
615
622
  names = [item["name"] for item in response.data["data"]["racks"]]
616
623
  self.assertEqual(names, ["Rack 2-1", "Rack 2-2"])
@@ -618,7 +625,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
618
625
  def test_graphql_api_token_super_user(self):
619
626
  """Validate a superuser can query everything."""
620
627
  response = self.clients[2].post(self.api_url, {"query": self.get_racks_query}, format="json")
621
- self.assertEqual(response.status_code, status.HTTP_200_OK)
628
+ self.assertHttpStatus(response, status.HTTP_200_OK)
622
629
  self.assertIsInstance(response.data["data"]["racks"], list)
623
630
  names = [item["name"] for item in response.data["data"]["racks"]]
624
631
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
@@ -626,7 +633,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
626
633
  def test_graphql_api_token_no_group(self):
627
634
  """Validate users with no permission are not able to query anything by default."""
628
635
  response = self.clients[3].post(self.api_url, {"query": self.get_racks_query}, format="json")
629
- self.assertEqual(response.status_code, status.HTTP_200_OK)
636
+ self.assertHttpStatus(response, status.HTTP_200_OK)
630
637
  self.assertIsInstance(response.data["data"]["racks"], list)
631
638
  names = [item["name"] for item in response.data["data"]["racks"]]
632
639
  self.assertEqual(names, [])
@@ -635,7 +642,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
635
642
  def test_graphql_api_token_no_group_exempt(self):
636
643
  """Validate users with no permission are able to query based on the exempt permissions."""
637
644
  response = self.clients[3].post(self.api_url, {"query": self.get_racks_query}, format="json")
638
- self.assertEqual(response.status_code, status.HTTP_200_OK)
645
+ self.assertHttpStatus(response, status.HTTP_200_OK)
639
646
  self.assertIsInstance(response.data["data"]["racks"], list)
640
647
  names = [item["name"] for item in response.data["data"]["racks"]]
641
648
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
@@ -643,18 +650,18 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
643
650
  def test_graphql_api_no_token(self):
644
651
  """Validate unauthenticated users are not able to query anything."""
645
652
  response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
646
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
653
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
647
654
 
648
655
  def test_graphql_api_wrong_token(self):
649
656
  """Validate a wrong token return 403."""
650
657
  self.client.credentials(HTTP_AUTHORIZATION="Token zzzzzzzzzzabcdef0123456789abcdef01234567")
651
658
  response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
652
- self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
659
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
653
660
 
654
661
  def test_graphql_query_params(self):
655
662
  """Validate query parameters are available for a model."""
656
663
  response = self.clients[2].post(self.api_url, {"query": self.get_racks_params_query}, format="json")
657
- self.assertEqual(response.status_code, status.HTTP_200_OK)
664
+ self.assertHttpStatus(response, status.HTTP_200_OK)
658
665
  self.assertIsInstance(response.data["data"]["racks"], list)
659
666
  names = [item["name"] for item in response.data["data"]["racks"]]
660
667
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2"])
@@ -663,14 +670,14 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
663
670
  """Validate graphql variables are working as expected."""
664
671
  payload = {"query": self.get_racks_var_query, "variables": {"location": "Location 1"}}
665
672
  response = self.clients[2].post(self.api_url, payload, format="json")
666
- self.assertEqual(response.status_code, status.HTTP_200_OK)
673
+ self.assertHttpStatus(response, status.HTTP_200_OK)
667
674
  self.assertIsInstance(response.data["data"]["racks"], list)
668
675
  names = [item["name"] for item in response.data["data"]["racks"]]
669
676
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2"])
670
677
 
671
678
  payload = {"query": self.get_racks_var_query, "variables": {"location": "Location 2"}}
672
679
  response = self.clients[2].post(self.api_url, payload, format="json")
673
- self.assertEqual(response.status_code, status.HTTP_200_OK)
680
+ self.assertHttpStatus(response, status.HTTP_200_OK)
674
681
  self.assertIsInstance(response.data["data"]["racks"], list)
675
682
  names = [item["name"] for item in response.data["data"]["racks"]]
676
683
  self.assertEqual(names, ["Rack 2-1", "Rack 2-2"])
@@ -679,14 +686,14 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
679
686
  """Validate graphql query for a single object as opposed to a set of objects also works."""
680
687
  payload = {"query": self.get_rack_query, "variables": {"id": Rack.objects.first().pk}}
681
688
  response = self.clients[2].post(self.api_url, payload, format="json")
682
- self.assertEqual(response.status_code, status.HTTP_200_OK)
689
+ self.assertHttpStatus(response, status.HTTP_200_OK)
683
690
  self.assertIsInstance(response.data["data"]["rack"], dict)
684
691
  self.assertEqual(response.data["data"]["rack"]["name"], Rack.objects.first().name)
685
692
 
686
693
  def test_graphql_query_multi_level(self):
687
694
  """Validate request with multiple levels return the proper information, following the permissions."""
688
695
  response = self.clients[0].post(self.api_url, {"query": self.get_locations_racks_query}, format="json")
689
- self.assertEqual(response.status_code, status.HTTP_200_OK)
696
+ self.assertHttpStatus(response, status.HTTP_200_OK)
690
697
  self.assertIsInstance(response.data["data"]["locations"], list)
691
698
  self.assertGreater(len(response.data["data"]["locations"]), 0)
692
699
  location_names = [item["name"] for item in response.data["data"]["locations"]]
@@ -702,7 +709,7 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
702
709
  data=self.get_locations_racks_query,
703
710
  content_type="application/graphql",
704
711
  )
705
- self.assertEqual(response.status_code, status.HTTP_200_OK)
712
+ self.assertHttpStatus(response, status.HTTP_200_OK)
706
713
  self.assertIsInstance(response.data["data"]["locations"], list)
707
714
  location_names = [item["name"] for item in response.data["data"]["locations"]]
708
715
  location_list = list(Location.objects.values_list("name", flat=True))
@@ -1114,14 +1121,12 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
1114
1121
  )
1115
1122
  cls.rm2ms_assoc_3.validated_save()
1116
1123
 
1117
- cls.backend = get_default_backend()
1118
-
1119
1124
  def execute_query(self, query, variables=None):
1120
- document = self.backend.document_from_string(self.SCHEMA, query)
1125
+ document = parse(query)
1121
1126
  if variables:
1122
- return document.execute(context_value=self.request, variable_values=variables)
1127
+ return execute(schema=self.SCHEMA, document=document, context_value=self.request, variable_values=variables)
1123
1128
  else:
1124
- return document.execute(context_value=self.request)
1129
+ return execute(schema=self.SCHEMA, document=document, context_value=self.request)
1125
1130
 
1126
1131
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1127
1132
  def test_query_circuit_terminations_cable_peer(self):
@@ -1522,7 +1527,7 @@ query {
1522
1527
 
1523
1528
  response = self.execute_query(query)
1524
1529
  self.assertEqual(len(response.errors), 1)
1525
- self.assertIsInstance(response.errors[0], GraphQLLocatedError)
1530
+ self.assertIsInstance(response.errors[0], GraphQLError)
1526
1531
 
1527
1532
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1528
1533
  def test_query_locations_filter(self):
@@ -1797,6 +1802,11 @@ query {
1797
1802
  f'device: "{self.device1.name}"',
1798
1803
  Q(device=self.device1),
1799
1804
  ),
1805
+ (
1806
+ f'type: "{PortTypeChoices.TYPE_8P8C}"',
1807
+ Q(type=PortTypeChoices.TYPE_8P8C),
1808
+ ),
1809
+ # for backward compatibility:
1800
1810
  (
1801
1811
  f'_type: "{PortTypeChoices.TYPE_8P8C}"',
1802
1812
  Q(type=PortTypeChoices.TYPE_8P8C),
@@ -1830,7 +1840,7 @@ query {
1830
1840
  Q(device=self.device1),
1831
1841
  ),
1832
1842
  (
1833
- f'_type: "{PortTypeChoices.TYPE_8P8C}"',
1843
+ f'type: "{PortTypeChoices.TYPE_8P8C}"',
1834
1844
  Q(type=PortTypeChoices.TYPE_8P8C),
1835
1845
  ),
1836
1846
  )
@@ -2436,7 +2446,7 @@ query {
2436
2446
  self.device1.primary_ip4 = self.ipaddr1
2437
2447
  self.device1.save()
2438
2448
  result = self.execute_query(query, variables={"device_id": str(self.device1.id)})
2439
- self.assertNotIn("error", str(result))
2449
+ self.assertIsNone(result.errors)
2440
2450
  expected_interfaces_first = {"ip_addresses": [{"primary_ip4_for": [{"id": str(self.device1.id)}]}]}
2441
2451
  self.assertEqual(result.data["device"]["interfaces"][0], expected_interfaces_first)
2442
2452
 
@@ -2450,12 +2460,12 @@ query {
2450
2460
  query = """query { tenant_groups { name tenants { name } } }"""
2451
2461
  # Prewarm caches of Relationships and such to reduce variation in the number of queries below
2452
2462
  result = self.execute_query(query)
2453
- self.assertNotIn("error", str(result))
2463
+ self.assertIsNone(result.errors)
2454
2464
  # Run it again and assert that the optimized query is run.
2455
2465
  # Before the fix for #7651 this would result in N+1 queries where N is the number of tenant-groups!
2456
2466
  with self.assertNumQueries(2):
2457
2467
  result = self.execute_query(query)
2458
- self.assertNotIn("error", str(result))
2468
+ self.assertIsNone(result.errors)
2459
2469
 
2460
2470
 
2461
2471
  class GraphQLTypeTestCase(UnitTestTestCase):
@@ -130,7 +130,7 @@ class NaturalKeyTestCase(BaseModelTest):
130
130
  self.FakeBaseModel._content_type_cached
131
131
  self.assertEqual(mock__content_type.call_count, 1)
132
132
 
133
- time.sleep(2) # Let the cache expire
133
+ time.sleep(3) # Let the cache expire
134
134
 
135
135
  self.FakeBaseModel._content_type_cached
136
136
  self.assertEqual(mock__content_type.call_count, 2)
@@ -13,6 +13,7 @@ from unittest import mock, TestCase
13
13
 
14
14
  from django import __version__ as django_version
15
15
  from django.conf import settings
16
+ from django.test import tag
16
17
 
17
18
  from nautobot import __version__ as nautobot_version
18
19
 
@@ -101,6 +102,7 @@ class NautobotServerTestCase(TestCase):
101
102
 
102
103
  self.assertNotEqual(secret_key_1, secret_key_2)
103
104
 
105
+ @tag("example_app")
104
106
  def test_settings_processing(self):
105
107
  result = subprocess.run(
106
108
  ["nautobot-server", "--config", settings.SETTINGS_PATH, "print_settings"],