nautobot 2.4.0b1__py3-none-any.whl → 2.4.1__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 (601) hide show
  1. nautobot/apps/__init__.py +1 -1
  2. nautobot/apps/api.py +8 -8
  3. nautobot/apps/change_logging.py +2 -2
  4. nautobot/apps/choices.py +4 -4
  5. nautobot/apps/events.py +3 -3
  6. nautobot/apps/factory.py +2 -2
  7. nautobot/apps/filters.py +1 -1
  8. nautobot/apps/forms.py +20 -20
  9. nautobot/apps/graphql.py +2 -2
  10. nautobot/apps/jobs.py +8 -8
  11. nautobot/apps/models.py +19 -19
  12. nautobot/apps/tables.py +1 -1
  13. nautobot/apps/testing.py +10 -10
  14. nautobot/apps/ui.py +2 -2
  15. nautobot/apps/utils.py +7 -7
  16. nautobot/apps/views.py +7 -7
  17. nautobot/circuits/api/serializers.py +1 -0
  18. nautobot/circuits/api/views.py +4 -8
  19. nautobot/circuits/tables.py +2 -1
  20. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  21. nautobot/circuits/views.py +3 -3
  22. nautobot/cloud/api/views.py +6 -10
  23. nautobot/cloud/models.py +1 -1
  24. nautobot/cloud/views.py +0 -16
  25. nautobot/core/api/constants.py +11 -0
  26. nautobot/core/api/fields.py +5 -5
  27. nautobot/core/api/filter_backends.py +3 -9
  28. nautobot/core/api/schema.py +13 -2
  29. nautobot/core/api/serializers.py +40 -34
  30. nautobot/core/api/views.py +56 -4
  31. nautobot/core/celery/log.py +4 -4
  32. nautobot/core/celery/schedulers.py +2 -2
  33. nautobot/core/choices.py +2 -2
  34. nautobot/core/events/__init__.py +3 -3
  35. nautobot/core/filters.py +67 -35
  36. nautobot/core/forms/__init__.py +19 -19
  37. nautobot/core/forms/fields.py +14 -11
  38. nautobot/core/forms/forms.py +33 -2
  39. nautobot/core/graphql/types.py +1 -1
  40. nautobot/core/jobs/__init__.py +28 -7
  41. nautobot/core/jobs/bulk_actions.py +285 -0
  42. nautobot/core/jobs/cleanup.py +48 -12
  43. nautobot/core/jobs/groups.py +1 -1
  44. nautobot/core/management/commands/validate_models.py +1 -1
  45. nautobot/core/models/__init__.py +3 -1
  46. nautobot/core/models/query_functions.py +2 -2
  47. nautobot/core/models/tree_queries.py +6 -3
  48. nautobot/core/settings.py +29 -2
  49. nautobot/core/settings.yaml +21 -0
  50. nautobot/core/tables.py +79 -61
  51. nautobot/core/templates/about.html +67 -0
  52. nautobot/core/templates/inc/media.html +3 -0
  53. nautobot/core/templates/inc/nav_menu.html +1 -0
  54. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  55. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  56. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  57. nautobot/core/templates/search.html +7 -0
  58. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  59. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  60. nautobot/core/templates/utilities/theme_preview.html +7 -0
  61. nautobot/core/templatetags/helpers.py +11 -2
  62. nautobot/core/testing/__init__.py +8 -8
  63. nautobot/core/testing/api.py +170 -15
  64. nautobot/core/testing/filters.py +45 -10
  65. nautobot/core/testing/forms.py +2 -0
  66. nautobot/core/testing/integration.py +86 -4
  67. nautobot/core/testing/mixins.py +7 -2
  68. nautobot/core/testing/views.py +44 -29
  69. nautobot/core/tests/integration/test_app_home.py +0 -1
  70. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  71. nautobot/core/tests/integration/test_filters.py +0 -2
  72. nautobot/core/tests/integration/test_home.py +0 -1
  73. nautobot/core/tests/integration/test_navbar.py +0 -1
  74. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  75. nautobot/core/tests/runner.py +1 -1
  76. nautobot/core/tests/test_api.py +98 -1
  77. nautobot/core/tests/test_csv.py +25 -3
  78. nautobot/core/tests/test_filters.py +209 -246
  79. nautobot/core/tests/test_forms.py +1 -0
  80. nautobot/core/tests/test_jobs.py +460 -1
  81. nautobot/core/tests/test_models.py +9 -0
  82. nautobot/core/tests/test_settings_schema.py +7 -0
  83. nautobot/core/tests/test_tables.py +100 -0
  84. nautobot/core/tests/test_utils.py +63 -1
  85. nautobot/core/tests/test_views.py +30 -3
  86. nautobot/core/ui/nav.py +1 -0
  87. nautobot/core/ui/object_detail.py +15 -1
  88. nautobot/core/urls.py +11 -0
  89. nautobot/core/utils/lookup.py +11 -8
  90. nautobot/core/utils/querysets.py +64 -0
  91. nautobot/core/utils/requests.py +24 -9
  92. nautobot/core/views/__init__.py +42 -0
  93. nautobot/core/views/generic.py +131 -197
  94. nautobot/core/views/mixins.py +126 -38
  95. nautobot/core/views/renderers.py +6 -6
  96. nautobot/dcim/api/serializers.py +56 -64
  97. nautobot/dcim/api/views.py +47 -113
  98. nautobot/dcim/constants.py +6 -13
  99. nautobot/dcim/factory.py +6 -1
  100. nautobot/dcim/filters/__init__.py +31 -2
  101. nautobot/dcim/forms.py +36 -17
  102. nautobot/dcim/graphql/types.py +2 -2
  103. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  104. nautobot/dcim/models/__init__.py +1 -1
  105. nautobot/dcim/models/device_component_templates.py +2 -2
  106. nautobot/dcim/models/device_components.py +22 -20
  107. nautobot/dcim/models/devices.py +10 -1
  108. nautobot/dcim/models/locations.py +3 -3
  109. nautobot/dcim/models/power.py +6 -5
  110. nautobot/dcim/models/racks.py +4 -4
  111. nautobot/dcim/tables/__init__.py +3 -3
  112. nautobot/dcim/tables/devices.py +7 -5
  113. nautobot/dcim/tables/devicetypes.py +2 -2
  114. nautobot/dcim/tables/racks.py +1 -1
  115. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  116. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  117. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  118. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  119. nautobot/dcim/templates/dcim/device.html +1 -9
  120. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  121. nautobot/dcim/templates/dcim/location.html +1 -9
  122. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  123. nautobot/dcim/templates/dcim/rack.html +1 -9
  124. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  125. nautobot/dcim/templates/dcim/rackreservation.html +1 -9
  126. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  127. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  128. nautobot/dcim/tests/integration/test_controller.py +62 -0
  129. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  130. nautobot/dcim/tests/integration/test_device_bulk_delete.py +189 -0
  131. nautobot/dcim/tests/integration/test_device_bulk_edit.py +181 -0
  132. nautobot/dcim/tests/test_api.py +16 -5
  133. nautobot/dcim/tests/test_filters.py +33 -0
  134. nautobot/dcim/tests/test_forms.py +51 -2
  135. nautobot/dcim/tests/test_graphql.py +52 -0
  136. nautobot/dcim/tests/test_jobs.py +118 -0
  137. nautobot/dcim/tests/test_models.py +52 -9
  138. nautobot/dcim/tests/test_views.py +21 -83
  139. nautobot/dcim/views.py +1 -13
  140. nautobot/extras/api/customfields.py +2 -2
  141. nautobot/extras/api/serializers.py +90 -85
  142. nautobot/extras/api/views.py +22 -27
  143. nautobot/extras/constants.py +2 -0
  144. nautobot/extras/filters/__init__.py +8 -6
  145. nautobot/extras/forms/base.py +2 -2
  146. nautobot/extras/forms/forms.py +139 -31
  147. nautobot/extras/forms/mixins.py +14 -6
  148. nautobot/extras/group_sync.py +3 -3
  149. nautobot/extras/health_checks.py +1 -2
  150. nautobot/extras/jobs.py +85 -18
  151. nautobot/extras/managers.py +3 -1
  152. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  153. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  154. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  155. nautobot/extras/models/__init__.py +1 -1
  156. nautobot/extras/models/contacts.py +1 -1
  157. nautobot/extras/models/customfields.py +12 -11
  158. nautobot/extras/models/groups.py +11 -9
  159. nautobot/extras/models/jobs.py +23 -4
  160. nautobot/extras/models/models.py +2 -2
  161. nautobot/extras/plugins/__init__.py +13 -2
  162. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  163. nautobot/extras/plugins/tables.py +16 -14
  164. nautobot/extras/plugins/views.py +65 -69
  165. nautobot/extras/registry.py +1 -1
  166. nautobot/extras/secrets/__init__.py +2 -2
  167. nautobot/extras/tables.py +7 -5
  168. nautobot/extras/templates/extras/dynamicgroup.html +1 -9
  169. nautobot/extras/templates/extras/job_detail.html +16 -0
  170. nautobot/extras/templates/extras/job_edit.html +1 -0
  171. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  172. nautobot/extras/templates/extras/marketplace.html +29 -11
  173. nautobot/extras/templates/extras/plugin_detail.html +32 -15
  174. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  175. nautobot/extras/templatetags/job_buttons.py +4 -4
  176. nautobot/extras/test_jobs/api_test_job.py +1 -1
  177. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  178. nautobot/extras/test_jobs/dry_run.py +1 -1
  179. nautobot/extras/test_jobs/fail.py +5 -5
  180. nautobot/extras/test_jobs/file_output.py +1 -1
  181. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  182. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  183. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  184. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  185. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  186. nautobot/extras/test_jobs/log_redaction.py +1 -1
  187. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  188. nautobot/extras/test_jobs/modify_db.py +1 -1
  189. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  190. nautobot/extras/test_jobs/object_var_required.py +1 -1
  191. nautobot/extras/test_jobs/object_vars.py +1 -1
  192. nautobot/extras/test_jobs/pass.py +3 -3
  193. nautobot/extras/test_jobs/profiling.py +1 -1
  194. nautobot/extras/test_jobs/relative_import.py +3 -3
  195. nautobot/extras/test_jobs/singleton.py +16 -0
  196. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  197. nautobot/extras/test_jobs/task_queues.py +1 -1
  198. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  199. nautobot/extras/tests/test_api.py +13 -13
  200. nautobot/extras/tests/test_customfields.py +1 -1
  201. nautobot/extras/tests/test_datasources.py +2 -1
  202. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  203. nautobot/extras/tests/test_filters.py +6 -6
  204. nautobot/extras/tests/test_forms.py +33 -1
  205. nautobot/extras/tests/test_jobs.py +178 -32
  206. nautobot/extras/tests/test_models.py +16 -10
  207. nautobot/extras/tests/test_plugins.py +62 -9
  208. nautobot/extras/tests/test_relationships.py +120 -9
  209. nautobot/extras/tests/test_views.py +56 -194
  210. nautobot/extras/utils.py +3 -2
  211. nautobot/extras/views.py +30 -98
  212. nautobot/ipam/api/fields.py +3 -3
  213. nautobot/ipam/api/serializers.py +41 -33
  214. nautobot/ipam/api/views.py +68 -117
  215. nautobot/ipam/factory.py +1 -1
  216. nautobot/ipam/filters.py +3 -2
  217. nautobot/ipam/lookups.py +101 -62
  218. nautobot/ipam/models.py +66 -16
  219. nautobot/ipam/querysets.py +2 -2
  220. nautobot/ipam/tables.py +23 -7
  221. nautobot/ipam/templates/ipam/ipaddress.html +1 -9
  222. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  223. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  224. nautobot/ipam/templates/ipam/prefix.html +1 -9
  225. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  226. nautobot/ipam/templates/ipam/vlan.html +1 -9
  227. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  228. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  229. nautobot/ipam/tests/test_api.py +436 -3
  230. nautobot/ipam/tests/test_forms.py +49 -47
  231. nautobot/ipam/tests/test_migrations.py +30 -30
  232. nautobot/ipam/tests/test_models.py +95 -34
  233. nautobot/ipam/tests/test_querysets.py +63 -1
  234. nautobot/ipam/tests/test_views.py +3 -0
  235. nautobot/ipam/utils/__init__.py +36 -6
  236. nautobot/ipam/views.py +61 -87
  237. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  238. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  239. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  240. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  241. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  242. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  243. nautobot/project-static/docs/404.html +46 -4
  244. nautobot/project-static/docs/apps/index.html +46 -4
  245. nautobot/project-static/docs/apps/nautobot-apps.html +47 -6
  246. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  247. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
  248. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
  249. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +62 -10
  250. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +59 -7
  251. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +374 -122
  252. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +90 -18
  253. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +95 -21
  254. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +53 -6
  255. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +52 -5
  256. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +79 -17
  257. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +102 -28
  258. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +108 -21
  259. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +131 -38
  260. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +239 -65
  261. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +581 -165
  262. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +109 -36
  263. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +453 -167
  264. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +493 -211
  265. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -8
  266. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +71 -15
  267. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +407 -55
  268. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +620 -205
  269. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +858 -412
  270. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +59 -7
  271. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +448 -186
  272. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +365 -147
  273. nautobot/project-static/docs/development/apps/api/configuration-view.html +46 -4
  274. nautobot/project-static/docs/development/apps/api/database-backend-config.html +46 -4
  275. nautobot/project-static/docs/development/apps/api/models/django-admin.html +46 -4
  276. nautobot/project-static/docs/development/apps/api/models/global-search.html +46 -4
  277. nautobot/project-static/docs/development/apps/api/models/graphql.html +46 -4
  278. nautobot/project-static/docs/development/apps/api/models/index.html +46 -4
  279. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +46 -4
  280. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +46 -4
  281. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +46 -4
  282. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +46 -4
  283. nautobot/project-static/docs/development/apps/api/platform-features/index.html +46 -4
  284. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +46 -4
  285. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +46 -4
  286. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +46 -4
  287. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +46 -4
  288. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +68 -7
  289. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +46 -4
  290. nautobot/project-static/docs/development/apps/api/prometheus.html +46 -4
  291. nautobot/project-static/docs/development/apps/api/setup.html +46 -4
  292. nautobot/project-static/docs/development/apps/api/testing.html +46 -4
  293. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +46 -4
  294. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +46 -4
  295. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +46 -4
  296. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +46 -4
  297. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +46 -4
  298. nautobot/project-static/docs/development/apps/api/views/base-template.html +46 -4
  299. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +46 -4
  300. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +46 -4
  301. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +46 -4
  302. nautobot/project-static/docs/development/apps/api/views/index.html +46 -4
  303. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +46 -4
  304. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +46 -4
  305. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +46 -4
  306. nautobot/project-static/docs/development/apps/api/views/notes.html +46 -4
  307. nautobot/project-static/docs/development/apps/api/views/rest-api.html +52 -6
  308. nautobot/project-static/docs/development/apps/api/views/urls.html +46 -4
  309. nautobot/project-static/docs/development/apps/index.html +46 -4
  310. nautobot/project-static/docs/development/apps/migration/code-updates.html +46 -4
  311. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +46 -4
  312. nautobot/project-static/docs/development/apps/migration/from-v1.html +46 -4
  313. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +46 -4
  314. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +46 -4
  315. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +46 -4
  316. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +46 -4
  317. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +50 -8
  318. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +46 -4
  319. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +211 -14
  320. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +46 -4
  321. nautobot/project-static/docs/development/apps/porting-from-netbox.html +46 -4
  322. nautobot/project-static/docs/development/core/application-registry.html +46 -4
  323. nautobot/project-static/docs/development/core/best-practices.html +46 -4
  324. nautobot/project-static/docs/development/core/bootstrap-ui.html +46 -4
  325. nautobot/project-static/docs/development/core/caching.html +46 -4
  326. nautobot/project-static/docs/development/core/controllers.html +46 -4
  327. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +73 -74
  328. nautobot/project-static/docs/development/core/generic-views.html +46 -4
  329. nautobot/project-static/docs/development/core/getting-started.html +249 -224
  330. nautobot/project-static/docs/development/core/homepage.html +49 -7
  331. nautobot/project-static/docs/development/core/index.html +46 -4
  332. nautobot/project-static/docs/development/core/{local-k8s.html → minikube-dev-environment-for-k8s-jobs.html} +469 -168
  333. nautobot/project-static/docs/development/core/model-checklist.html +56 -12
  334. nautobot/project-static/docs/development/core/model-features.html +46 -4
  335. nautobot/project-static/docs/development/core/natural-keys.html +46 -4
  336. nautobot/project-static/docs/development/core/navigation-menu.html +46 -4
  337. nautobot/project-static/docs/development/core/release-checklist.html +49 -7
  338. nautobot/project-static/docs/development/core/role-internals.html +46 -4
  339. nautobot/project-static/docs/development/core/settings.html +46 -4
  340. nautobot/project-static/docs/development/core/style-guide.html +49 -7
  341. nautobot/project-static/docs/development/core/templates.html +46 -4
  342. nautobot/project-static/docs/development/core/testing.html +46 -4
  343. nautobot/project-static/docs/development/core/ui-component-framework.html +369 -273
  344. nautobot/project-static/docs/development/core/user-preferences.html +46 -4
  345. nautobot/project-static/docs/development/index.html +46 -4
  346. nautobot/project-static/docs/development/jobs/index.html +216 -122
  347. nautobot/project-static/docs/development/jobs/migration/from-v1.html +46 -4
  348. nautobot/project-static/docs/index.html +54 -23
  349. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  350. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  351. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  352. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  353. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  354. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  355. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  356. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  357. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  358. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  359. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  360. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  361. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  362. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  363. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  364. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  365. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  366. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  367. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  368. nautobot/project-static/docs/objects.inv +0 -0
  369. nautobot/project-static/docs/overview/application_stack.html +47 -7
  370. nautobot/project-static/docs/overview/design_philosophy.html +46 -4
  371. nautobot/project-static/docs/release-notes/index.html +52 -12
  372. nautobot/project-static/docs/release-notes/version-1.0.html +234 -193
  373. nautobot/project-static/docs/release-notes/version-1.1.html +231 -190
  374. nautobot/project-static/docs/release-notes/version-1.2.html +306 -265
  375. nautobot/project-static/docs/release-notes/version-1.3.html +332 -291
  376. nautobot/project-static/docs/release-notes/version-1.4.html +417 -377
  377. nautobot/project-static/docs/release-notes/version-1.5.html +605 -566
  378. nautobot/project-static/docs/release-notes/version-1.6.html +904 -447
  379. nautobot/project-static/docs/release-notes/version-2.0.html +528 -489
  380. nautobot/project-static/docs/release-notes/version-2.1.html +363 -324
  381. nautobot/project-static/docs/release-notes/version-2.2.html +356 -317
  382. nautobot/project-static/docs/release-notes/version-2.3.html +997 -352
  383. nautobot/project-static/docs/release-notes/version-2.4.html +525 -101
  384. nautobot/project-static/docs/requirements.txt +2 -2
  385. nautobot/project-static/docs/search/search_index.json +1 -1
  386. nautobot/project-static/docs/sitemap.xml +295 -287
  387. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  388. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +46 -4
  389. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +46 -4
  390. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +48 -6
  391. nautobot/project-static/docs/user-guide/administration/configuration/index.html +46 -4
  392. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +46 -4
  393. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +110 -8
  394. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +46 -4
  395. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +46 -4
  396. nautobot/project-static/docs/user-guide/administration/guides/docker.html +46 -4
  397. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +46 -4
  398. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +46 -4
  399. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +46 -4
  400. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +46 -4
  401. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +46 -4
  402. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -6
  403. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +46 -4
  404. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +46 -4
  405. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +46 -4
  406. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +66 -8
  407. nautobot/project-static/docs/user-guide/administration/installation/index.html +46 -4
  408. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +47 -5
  409. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +46 -4
  410. nautobot/project-static/docs/user-guide/administration/installation/services.html +46 -4
  411. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +46 -4
  412. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +46 -4
  413. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +46 -4
  414. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +46 -4
  415. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +46 -4
  416. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +46 -4
  417. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +46 -4
  418. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +46 -4
  419. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +46 -4
  420. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +49 -8
  421. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +46 -4
  422. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +46 -4
  423. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +46 -4
  424. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +46 -4
  425. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +46 -4
  426. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +46 -4
  427. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +46 -4
  428. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +46 -4
  429. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +46 -4
  430. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +46 -4
  431. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +46 -4
  432. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +46 -4
  433. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +46 -4
  434. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +46 -4
  435. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +46 -4
  436. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +46 -4
  437. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +46 -4
  438. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +46 -4
  439. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +46 -4
  440. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +46 -4
  441. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +46 -4
  442. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +46 -4
  443. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +46 -4
  444. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +46 -4
  445. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +46 -4
  446. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +46 -4
  447. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +46 -4
  448. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +46 -4
  449. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +46 -4
  450. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +46 -4
  451. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +46 -4
  452. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +46 -4
  453. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +46 -4
  454. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +46 -4
  455. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +46 -4
  456. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +46 -4
  457. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +46 -4
  458. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +46 -4
  459. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +46 -4
  460. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +46 -4
  461. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +46 -4
  462. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +46 -4
  463. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +46 -4
  464. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +46 -4
  465. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +46 -4
  466. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +46 -4
  467. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +46 -4
  468. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +46 -4
  469. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +46 -4
  470. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +46 -4
  471. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +46 -4
  472. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +46 -4
  473. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +46 -4
  474. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +46 -4
  475. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +46 -4
  476. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +46 -4
  477. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +46 -4
  478. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +50 -12
  479. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +46 -4
  480. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +46 -4
  481. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +46 -4
  482. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +46 -4
  483. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +46 -4
  484. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +46 -4
  485. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +46 -4
  486. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +46 -4
  487. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +46 -4
  488. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +46 -4
  489. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +46 -4
  490. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +46 -4
  491. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +49 -7
  492. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +46 -4
  493. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +46 -4
  494. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +46 -4
  495. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +46 -4
  496. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +46 -4
  497. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +46 -4
  498. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +46 -4
  499. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +46 -4
  500. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +46 -4
  501. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +46 -4
  502. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +46 -4
  503. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +46 -4
  504. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +46 -4
  505. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +46 -4
  506. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +46 -4
  507. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +46 -4
  508. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +46 -4
  509. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +46 -4
  510. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +46 -4
  511. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +46 -4
  512. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +46 -4
  513. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +46 -4
  514. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +51 -7
  515. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +46 -4
  516. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  517. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  518. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  519. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  520. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  521. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  522. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  523. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  524. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  525. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  526. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  527. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  528. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  529. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +46 -4
  530. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +46 -4
  531. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +49 -7
  532. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
  533. nautobot/project-static/docs/user-guide/index.html +46 -4
  534. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +46 -4
  535. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +50 -8
  536. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +46 -4
  537. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +46 -4
  538. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +46 -4
  539. nautobot/project-static/docs/user-guide/platform-functionality/events.html +46 -4
  540. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +46 -4
  541. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +46 -4
  542. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +46 -4
  543. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +46 -4
  544. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +46 -4
  545. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +46 -4
  546. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +50 -7
  547. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +46 -4
  548. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +49 -7
  549. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +46 -4
  550. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +46 -4
  551. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
  552. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +46 -4
  553. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +46 -4
  554. nautobot/project-static/docs/user-guide/platform-functionality/note.html +46 -4
  555. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +46 -4
  556. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +47 -5
  557. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +46 -4
  558. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +46 -4
  559. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +94 -25
  560. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +74 -5
  561. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +46 -4
  562. nautobot/project-static/docs/user-guide/platform-functionality/role.html +46 -4
  563. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +46 -4
  564. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +46 -4
  565. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +46 -4
  566. nautobot/project-static/docs/user-guide/platform-functionality/status.html +46 -4
  567. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +46 -4
  568. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +46 -4
  569. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +46 -4
  570. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +46 -4
  571. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +46 -4
  572. nautobot/project-static/js/forms.js +1 -1
  573. nautobot/tenancy/api/views.py +9 -13
  574. nautobot/tenancy/views.py +4 -2
  575. nautobot/users/admin.py +1 -1
  576. nautobot/users/api/serializers.py +5 -4
  577. nautobot/users/api/views.py +3 -3
  578. nautobot/virtualization/api/serializers.py +4 -4
  579. nautobot/virtualization/api/views.py +5 -24
  580. nautobot/virtualization/filters.py +20 -3
  581. nautobot/virtualization/models.py +1 -1
  582. nautobot/virtualization/tables.py +2 -2
  583. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  584. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
  585. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  586. nautobot/virtualization/tests/test_filters.py +17 -0
  587. nautobot/wireless/filters.py +2 -2
  588. nautobot/wireless/forms.py +1 -1
  589. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  590. nautobot/wireless/tests/integration/__init__.py +0 -0
  591. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  592. nautobot/wireless/tests/test_filters.py +29 -1
  593. nautobot/wireless/tests/test_views.py +22 -1
  594. nautobot/wireless/views.py +0 -10
  595. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/METADATA +6 -6
  596. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/RECORD +600 -550
  597. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/WHEEL +1 -1
  598. nautobot/core/fixtures/user-data.json +0 -59
  599. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/LICENSE.txt +0 -0
  600. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/NOTICE +0 -0
  601. {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,181 @@
1
+ from django.urls import reverse
2
+
3
+ from nautobot.core.testing.integration import BulkOperationsMixin, ObjectsListMixin, SeleniumTestCase
4
+
5
+
6
+ class BulkEditDeviceTestCase(SeleniumTestCase, ObjectsListMixin, BulkOperationsMixin):
7
+ """
8
+ Test devices bulk delete.
9
+ """
10
+
11
+ def setUp(self):
12
+ super().setUp()
13
+
14
+ self.user.is_superuser = True
15
+ self.user.save()
16
+ self.login(self.user.username, self.password)
17
+
18
+ # Manufacturer
19
+ self.click_navbar_entry("Devices", "Manufacturers")
20
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:manufacturer_list"))
21
+ self.click_list_view_add_button()
22
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:manufacturer_add"))
23
+ self.browser.fill("name", "Test Manufacturer 1")
24
+ self.click_edit_form_create_button()
25
+
26
+ # Device Type
27
+ self.click_navbar_entry("Devices", "Device Types")
28
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:devicetype_list"))
29
+ self.click_list_view_add_button()
30
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:devicetype_add"))
31
+ self.fill_select2_field("manufacturer", "Test Manufacturer 1")
32
+ self.browser.fill("model", "Test Device Type 1")
33
+ self.click_edit_form_create_button()
34
+
35
+ # LocationType
36
+ self.click_navbar_entry("Organization", "Location Types")
37
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:locationtype_list"))
38
+ self.click_list_view_add_button()
39
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:locationtype_add"))
40
+ self.fill_select2_multiselect_field("content_types", "dcim | device")
41
+ self.browser.fill("name", "Test Location Type 1")
42
+ self.click_edit_form_create_button()
43
+
44
+ # Location 1
45
+ self.click_navbar_entry("Organization", "Locations")
46
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_list"))
47
+ self.click_list_view_add_button()
48
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_add"))
49
+ self.fill_select2_field("location_type", "Test Location Type 1")
50
+ self.fill_select2_field("status", "") # pick first status
51
+ self.browser.fill("name", "Test Location 1")
52
+ self.click_edit_form_create_button()
53
+
54
+ # Location 2
55
+ self.click_navbar_entry("Organization", "Locations")
56
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_list"))
57
+ self.click_list_view_add_button()
58
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_add"))
59
+ self.fill_select2_field("location_type", "Test Location Type 1")
60
+ self.fill_select2_field("status", "") # pick first status
61
+ self.browser.fill("name", "Test Location 2")
62
+ self.click_edit_form_create_button()
63
+
64
+ # Role
65
+ self.click_navbar_entry("Organization", "Roles")
66
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("extras:role_list"))
67
+ self.click_list_view_add_button()
68
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("extras:role_add"))
69
+ self.browser.fill("name", "Test Role 1")
70
+ self.fill_select2_multiselect_field("content_types", "dcim | device")
71
+ self.click_edit_form_create_button()
72
+
73
+ def _create_device(self, name, location="Test Location 1"):
74
+ self.click_navbar_entry("Devices", "Devices")
75
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_list"))
76
+ self.click_list_view_add_button()
77
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_add"))
78
+ self.browser.fill("name", name)
79
+ self.fill_select2_field("role", "Test Role 1")
80
+ self.fill_select2_field("device_type", "Test Device Type 1")
81
+ self.fill_select2_field("location", location)
82
+ self.fill_select2_field("status", "") # pick first status
83
+ self.click_edit_form_create_button()
84
+
85
+ def tearDown(self):
86
+ self.logout()
87
+ super().tearDown()
88
+
89
+ def _go_to_devices_list(self):
90
+ self.click_navbar_entry("Devices", "Devices")
91
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_list"))
92
+
93
+ def test_bulk_edit_require_selection(self):
94
+ self._go_to_devices_list()
95
+
96
+ # Click "edit selected" without selecting anything
97
+ self.click_bulk_edit()
98
+
99
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_list"))
100
+ self.assertTrue(self.browser.is_text_present("No devices were selected.", wait_time=5))
101
+
102
+ def test_bulk_edit_all_devices(self):
103
+ # Create device for test
104
+ self._create_device("Test Device Integration Test 1")
105
+ self._create_device("Test Device Integration Test 2")
106
+ self._go_to_devices_list()
107
+
108
+ # Select all devices and edit them
109
+ self.select_all_items()
110
+ self.click_bulk_edit()
111
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_bulk_edit"))
112
+
113
+ # Submit bulk edit form without any changes
114
+ self.submit_bulk_edit_operation()
115
+
116
+ # Verify job output
117
+ self.assertIsBulkEditJob()
118
+ self.assertJobStatusIsCompleted()
119
+
120
+ def test_bulk_edit_one_device(self):
121
+ # Create device for test
122
+ self._create_device("Test Device Integration Test 1")
123
+ self._create_device("Test Device Integration Test 2")
124
+ self._go_to_devices_list()
125
+
126
+ # Select one device and edit it
127
+ self.select_one_item()
128
+ self.click_bulk_edit()
129
+ self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_bulk_edit"))
130
+
131
+ # Submit bulk edit form without any changes
132
+ self.submit_bulk_edit_operation()
133
+
134
+ # Verify job output
135
+ self.assertIsBulkEditJob()
136
+ self.assertJobStatusIsCompleted()
137
+
138
+ def test_bulk_edit_all_filtered_devices(self):
139
+ # Create device for test
140
+ self._create_device("Test Device Integration Test 1")
141
+ self._create_device("Test Device Integration Test 2")
142
+ self._create_device("Test Device Integration Test 3", location="Test Location 2")
143
+ self._go_to_devices_list()
144
+
145
+ # Filter devices
146
+ self.apply_filter("location", "Test Location 2")
147
+
148
+ # Select all devices and edit them
149
+ self.select_all_items()
150
+ self.click_bulk_edit()
151
+ self.assertIn(self.live_server_url + reverse("dcim:device_bulk_edit"), self.browser.url)
152
+
153
+ # Submit bulk edit form without any changes
154
+ self.submit_bulk_edit_operation()
155
+
156
+ # Verify job output
157
+ self.assertIsBulkEditJob()
158
+ self.assertJobStatusIsCompleted()
159
+
160
+ def test_bulk_edit_one_filtered_devices(self):
161
+ # Create device for test
162
+ self._create_device("Test Device Integration Test 1")
163
+ self._create_device("Test Device Integration Test 2")
164
+ self._create_device("Test Device Integration Test 3", location="Test Location 2")
165
+ self._create_device("Test Device Integration Test 4", location="Test Location 2")
166
+ self._go_to_devices_list()
167
+
168
+ # Filter devices
169
+ self.apply_filter("location", "Test Location 2")
170
+
171
+ # Select one device and edit it
172
+ self.select_all_items()
173
+ self.click_bulk_edit()
174
+ self.assertIn(self.live_server_url + reverse("dcim:device_bulk_edit"), self.browser.url)
175
+
176
+ # Submit bulk edit form without any changes
177
+ self.submit_bulk_edit_operation()
178
+
179
+ # Verify job output
180
+ self.assertIsBulkEditJob()
181
+ self.assertJobStatusIsCompleted()
@@ -1469,11 +1469,24 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
1469
1469
  SecretsGroup.objects.create(name="Secrets Group 2"),
1470
1470
  )
1471
1471
 
1472
- device_type = DeviceType.objects.filter(software_image_files__isnull=False).first()
1472
+ device_type = DeviceType.objects.first()
1473
1473
  device_role = Role.objects.get_for_model(Device).first()
1474
1474
 
1475
- software_version = SoftwareVersion.objects.filter(software_image_files__device_types=device_type).first()
1476
- software_image_files = SoftwareImageFile.objects.exclude(software_version=software_version)[:2]
1475
+ software_version = SoftwareVersion.objects.first()
1476
+ software_image_files = (
1477
+ SoftwareImageFile.objects.create(
1478
+ status=Status.objects.get_for_model(SoftwareImageFile).first(),
1479
+ software_version=software_version,
1480
+ image_file_name="test_software_image_file_1.bin",
1481
+ ),
1482
+ SoftwareImageFile.objects.create(
1483
+ status=Status.objects.get_for_model(SoftwareImageFile).last(),
1484
+ software_version=software_version,
1485
+ image_file_name="test_software_image_file_2.bin",
1486
+ ),
1487
+ )
1488
+ for software_image_file in software_image_files:
1489
+ software_image_file.device_types.add(device_type)
1477
1490
 
1478
1491
  Device.objects.create(
1479
1492
  device_type=device_type,
@@ -3353,7 +3366,6 @@ class ControllerTestCase(APIViewTestCases.APIViewTestCase):
3353
3366
  "status": statuses[1].pk,
3354
3367
  "role": roles[1].pk,
3355
3368
  "location": locations[1].pk,
3356
- "capabilities": [],
3357
3369
  },
3358
3370
  {
3359
3371
  "name": "Controller 3",
@@ -3389,7 +3401,6 @@ class ControllerManagedDeviceGroupTestCase(APIViewTestCases.APIViewTestCase):
3389
3401
  "name": "ControllerManagedDeviceGroup 2",
3390
3402
  "controller": controllers[1].pk,
3391
3403
  "weight": 150,
3392
- "capabilities": [],
3393
3404
  },
3394
3405
  {
3395
3406
  "name": "ControllerManagedDeviceGroup 3",
@@ -1,3 +1,4 @@
1
+ # pylint: disable=no-member # it doesn't recognize the class attributes assigned in common_test_data()
1
2
  import uuid
2
3
 
3
4
  from django.contrib.auth import get_user_model
@@ -1691,6 +1692,7 @@ class DeviceTestCase(
1691
1692
  ("front_ports", "front_ports__id"),
1692
1693
  ("interfaces", "interfaces__id"),
1693
1694
  ("interfaces", "interfaces__name"),
1695
+ ("ip_addresses", "interfaces__ip_addresses__id"),
1694
1696
  ("mac_address", "interfaces__mac_address"),
1695
1697
  ("manufacturer", "device_type__manufacturer__id"),
1696
1698
  ("manufacturer", "device_type__manufacturer__name"),
@@ -1876,6 +1878,14 @@ class DeviceTestCase(
1876
1878
  Device.objects.filter(primary_ip4__isnull=True, primary_ip6__isnull=True),
1877
1879
  )
1878
1880
 
1881
+ def test_ip_addresses(self):
1882
+ addresses = list(IPAddress.objects.filter(interfaces__isnull=False)[:2])
1883
+ params = {"ip_addresses": [addresses[0].address, addresses[1].id]}
1884
+ self.assertQuerysetEqualAndNotEmpty(
1885
+ self.filterset(params, self.queryset).qs,
1886
+ self.queryset.filter(interfaces__ip_addresses__in=addresses).distinct(),
1887
+ )
1888
+
1879
1889
  def test_virtual_chassis_member(self):
1880
1890
  # TODO: Not a generic_filter_test because this is a boolean filter but not a RelatedMembershipBooleanFilter
1881
1891
  with self.subTest():
@@ -2131,6 +2141,7 @@ class InterfaceTestCase(PathEndpointModelTestMixin, ModularDeviceComponentTestMi
2131
2141
  ("child_interfaces", "child_interfaces__name"),
2132
2142
  ("description",),
2133
2143
  # ("device", "device__id"), # TODO - InterfaceFilterSet overrides device as a MultiValueCharFilter on name only
2144
+ ("ip_addresses", "ip_addresses__id"),
2134
2145
  ("label",),
2135
2146
  ("lag", "lag__id"),
2136
2147
  ("lag", "lag__name"),
@@ -2378,6 +2389,20 @@ class InterfaceTestCase(PathEndpointModelTestMixin, ModularDeviceComponentTestMi
2378
2389
  status=interface_statuses[3],
2379
2390
  )
2380
2391
 
2392
+ ipaddr_status = Status.objects.get_for_model(IPAddress).first()
2393
+ prefix_status = Status.objects.get_for_model(Prefix).first()
2394
+ namespace = Namespace.objects.first()
2395
+ Prefix.objects.create(prefix="192.0.2.0/24", namespace=namespace, status=prefix_status)
2396
+ Prefix.objects.create(prefix="2600::/64", namespace=namespace, status=prefix_status)
2397
+ ipaddresses = (
2398
+ IPAddress.objects.create(address="192.0.2.1/24", namespace=namespace, status=ipaddr_status),
2399
+ IPAddress.objects.create(address="192.0.2.2/24", namespace=namespace, status=ipaddr_status),
2400
+ IPAddress.objects.create(address="2600::1/120", namespace=namespace, status=ipaddr_status),
2401
+ IPAddress.objects.create(address="2600::0100/120", namespace=namespace, status=ipaddr_status),
2402
+ )
2403
+
2404
+ cabled_interfaces[0].add_ip_addresses([ipaddresses[0], ipaddresses[2]])
2405
+ cabled_interfaces[1].add_ip_addresses([ipaddresses[1], ipaddresses[3]])
2381
2406
  # Virtual Device Context
2382
2407
  vdc_status = Status.objects.get_for_model(VirtualDeviceContext).first()
2383
2408
  vdcs = [
@@ -2623,6 +2648,14 @@ class InterfaceTestCase(PathEndpointModelTestMixin, ModularDeviceComponentTestMi
2623
2648
  with self.subTest("device (pk) filter with an invalid uuid"):
2624
2649
  self.assertFalse(self.filterset({"device": [uuid.uuid4()]}, self.queryset).is_valid())
2625
2650
 
2651
+ def test_ip_addresses(self):
2652
+ addresses = list(IPAddress.objects.filter(interfaces__isnull=False)[:2])
2653
+ params = {"ip_addresses": [addresses[0].address, addresses[1].id]}
2654
+ self.assertQuerysetEqualAndNotEmpty(
2655
+ self.filterset(params, self.queryset).qs,
2656
+ self.queryset.filter(ip_addresses__in=addresses).distinct(),
2657
+ )
2658
+
2626
2659
  def test_kind(self):
2627
2660
  # TODO: Not a generic_filter_test because this is a single-value filter
2628
2661
  # 2.0 TODO: Support filtering for multiple values
@@ -1,8 +1,16 @@
1
1
  from django.test import TestCase
2
2
 
3
3
  from nautobot.core.testing.forms import FormTestCases
4
+ from nautobot.core.testing.mixins import NautobotTestCaseMixin
4
5
  from nautobot.dcim.choices import DeviceFaceChoices, InterfaceModeChoices, InterfaceTypeChoices, RackWidthChoices
5
- from nautobot.dcim.forms import DeviceFilterForm, DeviceForm, InterfaceCreateForm, InterfaceForm, RackForm
6
+ from nautobot.dcim.forms import (
7
+ DeviceFilterForm,
8
+ DeviceForm,
9
+ InterfaceBulkEditForm,
10
+ InterfaceCreateForm,
11
+ InterfaceForm,
12
+ RackForm,
13
+ )
6
14
  from nautobot.dcim.models import (
7
15
  Device,
8
16
  DeviceType,
@@ -320,7 +328,7 @@ class RackTestCase(TestCase):
320
328
  self.assertTrue(form.is_valid())
321
329
 
322
330
 
323
- class InterfaceTestCase(TestCase):
331
+ class InterfaceTestCase(NautobotTestCaseMixin, TestCase):
324
332
  @classmethod
325
333
  def setUpTestData(cls):
326
334
  cls.device = Device.objects.first()
@@ -380,3 +388,44 @@ class InterfaceTestCase(TestCase):
380
388
  self.vlan.locations.clear()
381
389
  form = InterfaceForm(data=self.data, instance=self.interface)
382
390
  self.assertTrue(form.is_valid())
391
+
392
+ def test_untagged_vlans_dropdown_options_align_in_interface_edit_form_and_bulk_edit_form(self):
393
+ """
394
+ Assert that untagged_vlans field dropdown are populated correctly in InterfaceForm and InterfaceBulkEditForm,
395
+ and that the queryset is the same for both forms.
396
+ """
397
+ status = Status.objects.get_for_model(Interface).first()
398
+ location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
399
+ devices = Device.objects.all()[:3]
400
+ for device in devices:
401
+ device.location = location
402
+ device.save()
403
+ interfaces = (
404
+ Interface.objects.create(
405
+ device=devices[0],
406
+ name="Test Interface 1",
407
+ type=InterfaceTypeChoices.TYPE_2GFC_SFP,
408
+ status=status,
409
+ ),
410
+ Interface.objects.create(
411
+ device=devices[1],
412
+ name="Test Interface 2",
413
+ type=InterfaceTypeChoices.TYPE_LAG,
414
+ status=status,
415
+ ),
416
+ Interface.objects.create(
417
+ device=devices[2],
418
+ name="Test Interface 3",
419
+ type=InterfaceTypeChoices.TYPE_100ME_FIXED,
420
+ status=status,
421
+ ),
422
+ )
423
+ edit_form = InterfaceForm(data=self.data, instance=interfaces[0])
424
+ bulk_edit_form = InterfaceBulkEditForm(
425
+ model=Interface,
426
+ data={"pks": [interface.pk for interface in interfaces]},
427
+ )
428
+ self.assertQuerysetEqualAndNotEmpty(
429
+ edit_form.fields["untagged_vlan"].queryset,
430
+ bulk_edit_form.fields["untagged_vlan"].queryset,
431
+ )
@@ -83,3 +83,55 @@ class GraphQLTestCase(TestCase):
83
83
  self.assertEqual(
84
84
  resp["data"]["devices"][0]["platform"]["network_driver_mappings"]["scrapli"], "cisco_iosxe"
85
85
  )
86
+
87
+ with self.subTest("device serial number query"):
88
+ non_empty_serial_device = Device.objects.first()
89
+ non_empty_serial_device.serial = "1234567890abceFGHIJKL"
90
+ non_empty_serial_device.save()
91
+
92
+ # Test device serial query default behavior: serial__ie
93
+ query = 'query { devices (serial: " ") { name serial } }'
94
+ resp = execute_query(query, user=self.user).to_dict()
95
+ self.assertFalse(resp["data"].get("error"))
96
+ for device in resp["data"]["devices"]:
97
+ self.assertEqual(device["serial"], "")
98
+
99
+ # Test device serial default filter with non-empty serial number
100
+ query = 'query { devices (serial:"' + non_empty_serial_device.serial.lower() + '") { name serial } }'
101
+ resp = execute_query(query, user=self.user).to_dict()
102
+ self.assertFalse(resp["data"].get("error"))
103
+ self.assertEqual(resp["data"]["devices"][0]["serial"], non_empty_serial_device.serial)
104
+
105
+ # Test device serial iexact filter with non-empty serial number
106
+ query = 'query { devices (serial__ie:"' + non_empty_serial_device.serial.upper() + '") { name serial } }'
107
+ resp = execute_query(query, user=self.user).to_dict()
108
+ self.assertFalse(resp["data"].get("error"))
109
+ self.assertEqual(resp["data"]["devices"][0]["serial"], non_empty_serial_device.serial)
110
+
111
+ # Test device serial__nie filter with non-empty serial number
112
+ query = 'query { devices (serial__nie:"' + non_empty_serial_device.serial.lower() + '") { name serial } }'
113
+ resp = execute_query(query, user=self.user).to_dict()
114
+ self.assertFalse(resp["data"].get("error"))
115
+ for device in resp["data"]["devices"]:
116
+ self.assertNotEqual(device["serial"], non_empty_serial_device.serial)
117
+
118
+ # Test device serial__ie filter with empty serial number
119
+ query = 'query { devices (serial__ie:" ") { name serial } }'
120
+ resp = execute_query(query, user=self.user).to_dict()
121
+ self.assertFalse(resp["data"].get("error"))
122
+ for device in resp["data"]["devices"]:
123
+ self.assertEqual(device["serial"], "")
124
+
125
+ # Test device serial__nie filter with empty serial number
126
+ query = 'query { devices (serial__nie:" ") { name serial } }'
127
+ resp = execute_query(query, user=self.user).to_dict()
128
+ self.assertFalse(resp["data"].get("error"))
129
+ for device in resp["data"]["devices"]:
130
+ self.assertNotEqual(device["serial"], "")
131
+
132
+ # Test device serial__n filter with empty serial number
133
+ query = 'query { devices (serial__n:" ") { name serial } }'
134
+ resp = execute_query(query, user=self.user).to_dict()
135
+ self.assertFalse(resp["data"].get("error"))
136
+ for device in resp["data"]["devices"]:
137
+ self.assertNotEqual(device["serial"], "")
@@ -0,0 +1,118 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+
3
+ from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
4
+ from nautobot.dcim.models import (
5
+ Device,
6
+ DeviceType,
7
+ DeviceTypeToSoftwareImageFile,
8
+ Location,
9
+ LocationType,
10
+ Manufacturer,
11
+ Platform,
12
+ SoftwareImageFile,
13
+ SoftwareVersion,
14
+ )
15
+ from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
16
+ from nautobot.extras.models import JobLogEntry, Role, Status
17
+
18
+
19
+ def create_common_data_for_software_related_test_cases():
20
+ manufacturer = Manufacturer.objects.create(name="Manufacturer 1")
21
+ Platform.objects.create(name="Platform 1")
22
+ platform = Platform.objects.create(name="Platform 2", manufacturer=manufacturer)
23
+
24
+ status = Status.objects.first()
25
+ software_version_ct = ContentType.objects.get_for_model(SoftwareVersion)
26
+ software_image_file_ct = ContentType.objects.get_for_model(SoftwareImageFile)
27
+ status.content_types.add(software_version_ct)
28
+ status.content_types.add(software_image_file_ct)
29
+
30
+ software_version = SoftwareVersion.objects.create(
31
+ platform=platform,
32
+ version="Test version 1.0.0",
33
+ status=status,
34
+ )
35
+ software_image_file = SoftwareImageFile.objects.create(
36
+ software_version=software_version,
37
+ image_file_name="software_image_file_qs_test_1.bin",
38
+ status=status,
39
+ )
40
+
41
+ device_type = DeviceType.objects.create(
42
+ manufacturer=manufacturer,
43
+ model="Cisco CSR1000v",
44
+ u_height=0,
45
+ )
46
+
47
+ device_ct = ContentType.objects.get_for_model(Device)
48
+
49
+ device_role = Role.objects.create(name="Device Status")
50
+ device_role.content_types.set([device_ct])
51
+
52
+ location_type = LocationType.objects.create(name="Test Location Type")
53
+ location_type.content_types.set([device_ct])
54
+ location = Location.objects.create(
55
+ name="Device Location",
56
+ location_type=location_type,
57
+ status=Status.objects.get_for_model(Location).first(),
58
+ )
59
+ device_status = Status.objects.get_for_model(Device).first()
60
+ Device.objects.create(
61
+ device_type=device_type,
62
+ role=device_role,
63
+ name="Device 1",
64
+ location=location,
65
+ status=device_status,
66
+ software_version=software_version,
67
+ )
68
+ DeviceTypeToSoftwareImageFile.objects.create(device_type=device_type, software_image_file=software_image_file)
69
+
70
+
71
+ class TestSoftwareImageFileTestCase(TransactionTestCase):
72
+ def test_correct_handling_for_model_protected_error(self):
73
+ create_common_data_for_software_related_test_cases()
74
+ software_image_file = SoftwareImageFile.objects.first()
75
+
76
+ self.add_permissions("dcim.delete_softwareimagefile")
77
+ pk_list = [str(software_image_file.pk)]
78
+ initial_count = SoftwareImageFile.objects.all().count()
79
+
80
+ job_result = create_job_result_and_run_job(
81
+ "nautobot.core.jobs.bulk_actions",
82
+ "BulkDeleteObjects",
83
+ content_type=ContentType.objects.get_for_model(SoftwareImageFile).id,
84
+ delete_all=False,
85
+ filter_query_params={},
86
+ pk_list=pk_list,
87
+ username=self.user.username,
88
+ )
89
+ logs = JobLogEntry.objects.filter(job_result=job_result)
90
+ print([(log.message, log.log_level) for log in logs])
91
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
92
+ error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
93
+ self.assertIn("Caught ProtectedError while attempting to delete objects", error_log.message)
94
+ self.assertEqual(initial_count, SoftwareImageFile.objects.all().count())
95
+
96
+
97
+ class TestSoftwareVersionTestCase(TransactionTestCase):
98
+ def test_correct_handling_for_model_protected_error(self):
99
+ create_common_data_for_software_related_test_cases()
100
+ software_version = SoftwareVersion.objects.first()
101
+
102
+ initial_count = SoftwareVersion.objects.all().count()
103
+ self.add_permissions("dcim.delete_softwareversion")
104
+ pk_list = [str(software_version.pk)]
105
+
106
+ job_result = create_job_result_and_run_job(
107
+ "nautobot.core.jobs.bulk_actions",
108
+ "BulkDeleteObjects",
109
+ content_type=ContentType.objects.get_for_model(SoftwareVersion).id,
110
+ delete_all=False,
111
+ filter_query_params={},
112
+ pk_list=pk_list,
113
+ username=self.user.username,
114
+ )
115
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
116
+ error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
117
+ self.assertIn("Caught ProtectedError while attempting to delete objects", error_log.message)
118
+ self.assertEqual(initial_count, SoftwareVersion.objects.all().count())
@@ -4,6 +4,7 @@ from constance.test import override_config
4
4
  from django.contrib.contenttypes.models import ContentType
5
5
  from django.core.exceptions import ValidationError
6
6
  from django.db import IntegrityError
7
+ from django.db.models import Model
7
8
  from django.test import TestCase
8
9
  from django.test.utils import override_settings
9
10
 
@@ -77,7 +78,7 @@ class ModularDeviceComponentTestCaseMixin:
77
78
 
78
79
  # fields required to create instances of the model, with the exception of name, device_field and module_field
79
80
  modular_component_create_data = {}
80
- model = None
81
+ model: type[Model]
81
82
  device_field = "device" # field name for the parent device
82
83
  module_field = "module" # field name for the parent module
83
84
 
@@ -1954,6 +1955,8 @@ class CableTestCase(ModelTestCases.BaseModelTestCase):
1954
1955
 
1955
1956
  @classmethod
1956
1957
  def setUpTestData(cls):
1958
+ cls.interface_choices = {section[0]: dict(section[1]) for section in InterfaceTypeChoices.CHOICES}
1959
+
1957
1960
  location = Location.objects.first()
1958
1961
  manufacturer = Manufacturer.objects.first()
1959
1962
  devicetype = DeviceType.objects.create(
@@ -2182,19 +2185,59 @@ class CableTestCase(ModelTestCases.BaseModelTestCase):
2182
2185
  """
2183
2186
  A cable cannot terminate to a virtual interface
2184
2187
  """
2185
- virtual_interface = Interface(device=self.device1, name="V1", type=InterfaceTypeChoices.TYPE_VIRTUAL)
2186
- cable = Cable(termination_a=self.interface2, termination_b=virtual_interface)
2187
- with self.assertRaises(ValidationError):
2188
- cable.clean()
2188
+ virtual_interface_choices = self.interface_choices["Virtual interfaces"]
2189
+
2190
+ self.cable.delete()
2191
+ interface_status = Status.objects.get_for_model(Interface).first()
2192
+
2193
+ for virtual_interface_type in virtual_interface_choices:
2194
+ virtual_interface = Interface.objects.create(
2195
+ device=self.device1,
2196
+ name=f"V-{virtual_interface_type}",
2197
+ type=virtual_interface_type,
2198
+ status=interface_status,
2199
+ )
2200
+ with self.assertRaises(
2201
+ ValidationError,
2202
+ msg=f"Virtual interface type '{virtual_interface_choices[virtual_interface_type]}' should not accept a cable.",
2203
+ ) as cm:
2204
+ Cable(
2205
+ termination_a=self.interface2,
2206
+ termination_b=virtual_interface,
2207
+ ).clean()
2208
+
2209
+ self.assertIn(
2210
+ f"Cables cannot be terminated to {virtual_interface_choices[virtual_interface_type]} interfaces",
2211
+ str(cm.exception),
2212
+ )
2189
2213
 
2190
2214
  def test_cable_cannot_terminate_to_a_wireless_interface(self):
2191
2215
  """
2192
2216
  A cable cannot terminate to a wireless interface
2193
2217
  """
2194
- wireless_interface = Interface(device=self.device1, name="W1", type=InterfaceTypeChoices.TYPE_80211A)
2195
- cable = Cable(termination_a=self.interface2, termination_b=wireless_interface)
2196
- with self.assertRaises(ValidationError):
2197
- cable.clean()
2218
+ wireless_interface_choices = self.interface_choices["Wireless"]
2219
+
2220
+ self.cable.delete()
2221
+ interface_status = Status.objects.get_for_model(Interface).first()
2222
+
2223
+ for wireless_interface_type in wireless_interface_choices:
2224
+ wireless_interface = Interface.objects.create(
2225
+ device=self.device1,
2226
+ name=f"W-{wireless_interface_type}",
2227
+ type=wireless_interface_type,
2228
+ status=interface_status,
2229
+ )
2230
+
2231
+ with self.assertRaises(
2232
+ ValidationError,
2233
+ msg=f"Wireless interface type '{wireless_interface_choices[wireless_interface_type]}' should not accept a cable.",
2234
+ ) as cm:
2235
+ Cable(termination_a=self.interface2, termination_b=wireless_interface).clean()
2236
+
2237
+ self.assertIn(
2238
+ f"Cables cannot be terminated to {wireless_interface_choices[wireless_interface_type]} interfaces",
2239
+ str(cm.exception),
2240
+ )
2198
2241
 
2199
2242
  def test_create_cable_with_missing_status_connected(self):
2200
2243
  """Test for https://github.com/nautobot/nautobot/issues/2081"""