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
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import ClassVar, Optional
2
3
 
3
4
  from django.contrib import messages
4
5
  from django.contrib.auth.mixins import AccessMixin
@@ -22,6 +23,7 @@ from django.utils.encoding import iri_to_uri
22
23
  from django.utils.html import format_html
23
24
  from django.utils.http import url_has_allowed_host_and_scheme
24
25
  from django.views.generic.edit import FormView
26
+ from django_filters import FilterSet
25
27
  from drf_spectacular.utils import extend_schema
26
28
  from rest_framework import exceptions, mixins
27
29
  from rest_framework.decorators import action as drf_action
@@ -29,6 +31,7 @@ from rest_framework.parsers import FormParser, MultiPartParser
29
31
  from rest_framework.response import Response
30
32
  from rest_framework.viewsets import GenericViewSet
31
33
 
34
+ from nautobot.core import exceptions as core_exceptions
32
35
  from nautobot.core.api.views import BulkDestroyModelMixin, BulkUpdateModelMixin
33
36
  from nautobot.core.forms import (
34
37
  BootstrapMixin,
@@ -37,7 +40,8 @@ from nautobot.core.forms import (
37
40
  CSVFileField,
38
41
  restrict_form_fields,
39
42
  )
40
- from nautobot.core.utils import lookup, permissions
43
+ from nautobot.core.jobs import BulkDeleteObjects, BulkEditObjects
44
+ from nautobot.core.utils import filtering, lookup, permissions
41
45
  from nautobot.core.utils.requests import get_filterable_params_from_filter_params, normalize_querydict
42
46
  from nautobot.core.views.renderers import NautobotHTMLRenderer
43
47
  from nautobot.core.views.utils import (
@@ -48,7 +52,7 @@ from nautobot.core.views.utils import (
48
52
  )
49
53
  from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
50
54
  from nautobot.extras.forms import NoteForm
51
- from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
55
+ from nautobot.extras.models import ExportTemplate, Job, JobResult, SavedView, UserSavedViewAssociation
52
56
  from nautobot.extras.tables import NoteTable, ObjectChangeTable
53
57
  from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, get_base_template, remove_prefix_from_cf_key
54
58
 
@@ -223,7 +227,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
223
227
  # filterset and filter_params will be initialized in filter_queryset() in ObjectListViewMixin
224
228
  filter_params = None
225
229
  filterset = None
226
- filterset_class = None
230
+ filterset_class: Optional[type[FilterSet]] = None
227
231
  filterset_form_class = None
228
232
  form_class = None
229
233
  create_form_class = None
@@ -473,7 +477,11 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
473
477
  def get_filter_params(self, request):
474
478
  """Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
475
479
  params = request.GET.copy()
476
- filter_params = get_filterable_params_from_filter_params(params, self.non_filter_params, self.filterset_class())
480
+ filter_params = get_filterable_params_from_filter_params(
481
+ params,
482
+ self.non_filter_params,
483
+ self.filterset_class(), # pylint: disable=not-callable # only called if filterset_class is not None
484
+ )
477
485
  if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
478
486
  return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
479
487
  return filter_params
@@ -579,11 +587,11 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
579
587
  if self.action == "bulk_destroy":
580
588
  queryset = self.get_queryset()
581
589
  bulk_delete_all = bool(self.request.POST.get("_all"))
582
- if bulk_delete_all:
583
- return ConfirmationForm
584
590
 
585
591
  class BulkDestroyForm(ConfirmationForm):
586
- pk = ModelMultipleChoiceField(queryset=queryset, widget=MultipleHiddenInput)
592
+ pk = ModelMultipleChoiceField(
593
+ queryset=queryset, widget=MultipleHiddenInput, required=not bulk_delete_all
594
+ )
587
595
 
588
596
  return BulkDestroyForm
589
597
  else:
@@ -629,8 +637,8 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
629
637
  """
630
638
 
631
639
  action_buttons = ("add", "import", "export")
632
- filterset_class = None
633
- filterset_form_class = None
640
+ filterset_class: Optional[type[FilterSet]] = None
641
+ filterset_form_class: Optional[type[Form]] = None
634
642
  hide_hierarchy_ui = False
635
643
  non_filter_params = (
636
644
  "export", # trigger for CSV/export-template/YAML export # 3.0 TODO: remove, irrelevant after #4746
@@ -780,7 +788,8 @@ class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
780
788
  return self.perform_destroy(request, **kwargs)
781
789
  return Response(context)
782
790
 
783
- def perform_destroy(self, request, **kwargs):
791
+ # TODO: this conflicts with DRF's DestroyModelMixin.perform_destroy(self, instance) API
792
+ def perform_destroy(self, request, **kwargs): # pylint:disable=arguments-renamed
784
793
  """
785
794
  Function to validate the ObjectDeleteConfirmationForm and to delete the object.
786
795
  """
@@ -912,11 +921,13 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
912
921
  return self.form_invalid(form)
913
922
 
914
923
 
915
- class EditAndDeleteAllModelMixin:
924
+ class BulkEditAndBulkDeleteModelMixin:
916
925
  """
917
- UI mixin to bulk destroy all and bulk edit all model instances.
926
+ UI mixin to bulk destroy and bulk edit all model instances.
918
927
  """
919
928
 
929
+ logger = logging.getLogger(__name__)
930
+
920
931
  def _get_bulk_edit_delete_all_queryset(self, request):
921
932
  """
922
933
  Retrieve the queryset of model instances to be bulk-deleted or bulk-deleted, filtered based on request parameters.
@@ -934,7 +945,7 @@ class EditAndDeleteAllModelMixin:
934
945
  filterset_class = getattr(self, "filterset_class", None)
935
946
 
936
947
  if request.GET and filterset_class is not None:
937
- queryset = filterset_class(request.GET, model.objects.all()).qs
948
+ queryset = filterset_class(request.GET, model.objects.all()).qs # pylint: disable=not-callable
938
949
  # We take this approach because filterset.qs has already applied .distinct(),
939
950
  # and performing a .delete directly on a queryset with .distinct applied is not allowed.
940
951
  queryset = self.queryset.filter(pk__in=queryset)
@@ -942,24 +953,92 @@ class EditAndDeleteAllModelMixin:
942
953
  queryset = model.objects.all()
943
954
  return queryset
944
955
 
945
- def _bulk_delete_all_context(self, request, queryset):
946
- model = queryset.model
947
- return {
948
- "obj_type_plural": model._meta.verbose_name_plural,
949
- "return_url": self.get_return_url(request),
950
- "total_objs_to_delete": queryset.count(),
951
- "delete_all": True,
952
- "table": None,
953
- }
956
+ def send_bulk_delete_objects_to_job(self, request, pk_list, model, delete_all):
957
+ """Prepare and enqueue bulk delete job."""
958
+ job_model = Job.objects.get_for_class_path(BulkDeleteObjects.class_path)
954
959
 
960
+ if filterset_class := lookup.get_filterset_for_model(model):
961
+ filter_query_params = normalize_querydict(request.GET, filterset=filterset_class())
962
+ else:
963
+ filter_query_params = {}
964
+
965
+ # Discarding non-filter query params
966
+ new_filter_query_params = {}
967
+
968
+ for key, value in filter_query_params.items():
969
+ try:
970
+ filtering.get_filterset_field(filterset_class(), key)
971
+ new_filter_query_params[key] = value
972
+ except core_exceptions.FilterSetFieldNotFound:
973
+ self.logger.debug(f"Query parameter `{key}` not found in `{filterset_class}`, discarding it")
974
+
975
+ filter_query_params = new_filter_query_params
976
+
977
+ job_form = BulkDeleteObjects.as_form(
978
+ data={
979
+ "pk_list": pk_list,
980
+ "content_type": ContentType.objects.get_for_model(model),
981
+ "delete_all": delete_all,
982
+ "filter_query_params": filter_query_params,
983
+ }
984
+ )
985
+ # BulkDeleteObjects job form cannot be invalid; Hence no handling of invalid case.
986
+ job_form.is_valid()
987
+ job_kwargs = BulkDeleteObjects.prepare_job_kwargs(job_form.cleaned_data)
988
+ job_result = JobResult.enqueue_job(
989
+ job_model,
990
+ request.user,
991
+ **BulkDeleteObjects.serialize_data(job_kwargs),
992
+ )
993
+ return redirect("extras:jobresult", pk=job_result.pk)
994
+
995
+ def send_bulk_edit_objects_to_job(self, request, form, model):
996
+ """Prepare and enqueue a bulk edit job."""
997
+ job_model = Job.objects.get_for_class_path(BulkEditObjects.class_path)
998
+ form_data = normalize_querydict(request.POST, form)
999
+ if filterset_class := lookup.get_filterset_for_model(model):
1000
+ filter_query_params = normalize_querydict(request.GET, filterset=filterset_class())
1001
+ else:
1002
+ filter_query_params = {}
955
1003
 
956
- class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, EditAndDeleteAllModelMixin):
1004
+ # Discarding non-filter query params
1005
+ new_filter_query_params = {}
1006
+
1007
+ for key, value in filter_query_params.items():
1008
+ try:
1009
+ filtering.get_filterset_field(filterset_class(), key)
1010
+ new_filter_query_params[key] = value
1011
+ except core_exceptions.FilterSetFieldNotFound:
1012
+ self.logger.debug(f"Query parameter `{key}` not found in `{filterset_class}`, discarding it")
1013
+
1014
+ filter_query_params = new_filter_query_params
1015
+
1016
+ job_form = BulkEditObjects.as_form(
1017
+ data={
1018
+ "form_data": form_data,
1019
+ "content_type": ContentType.objects.get_for_model(model),
1020
+ "edit_all": request.POST.get("_all") is not None,
1021
+ "filter_query_params": filter_query_params,
1022
+ }
1023
+ )
1024
+ # NOTE: BulkEditObjects cant be invalid, so there is no need for handling invalid error
1025
+ job_form.is_valid()
1026
+ job_kwargs = BulkEditObjects.prepare_job_kwargs(job_form.cleaned_data)
1027
+ job_result = JobResult.enqueue_job(
1028
+ job_model,
1029
+ request.user,
1030
+ **BulkEditObjects.serialize_data(job_kwargs),
1031
+ )
1032
+ return redirect("extras:jobresult", pk=job_result.pk)
1033
+
1034
+
1035
+ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, BulkEditAndBulkDeleteModelMixin):
957
1036
  """
958
1037
  UI mixin to bulk destroy model instances.
959
1038
  """
960
1039
 
961
- bulk_destroy_form_class = None
962
- filterset_class = None
1040
+ bulk_destroy_form_class: Optional[type[Form]] = None
1041
+ filterset_class: Optional[type[FilterSet]] = None
963
1042
 
964
1043
  def _process_bulk_destroy_form(self, form):
965
1044
  request = self.request
@@ -992,7 +1071,8 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
992
1071
  """
993
1072
  return self.perform_bulk_destroy(request, **kwargs)
994
1073
 
995
- def perform_bulk_destroy(self, request, **kwargs):
1074
+ # TODO: this conflicts with BulkDestroyModelMixin.perform_bulk_destroy(self, objects)
1075
+ def perform_bulk_destroy(self, request, **kwargs): # pylint:disable=arguments-renamed
996
1076
  """
997
1077
  request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer.
998
1078
  request.POST "_confirm": Function to validate the table form/BulkDestroyConfirmationForm and to perform the action of bulk destroy. Render the form with errors if exceptions are raised.
@@ -1002,21 +1082,23 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
1002
1082
  data = {}
1003
1083
  # Are we deleting *all* objects in the queryset or just a selected subset?
1004
1084
  if delete_all:
1085
+ self.pk_list = []
1005
1086
  queryset = self._get_bulk_edit_delete_all_queryset(self.request)
1006
- data = self._bulk_delete_all_context(request, queryset)
1007
1087
  else:
1008
1088
  self.pk_list = list(request.POST.getlist("pk"))
1089
+ queryset = queryset.filter(pk__in=self.pk_list)
1009
1090
 
1010
- form_class = self.get_form_class(**kwargs)
1011
1091
  if "_confirm" in request.POST:
1092
+ form_class = self.get_form_class(**kwargs)
1012
1093
  form = form_class(request.POST, initial=normalize_querydict(request.GET, form_class=form_class))
1013
1094
  if form.is_valid():
1014
- return self.form_valid(form)
1095
+ return self.send_bulk_delete_objects_to_job(request, self.pk_list, queryset.model, delete_all)
1015
1096
  else:
1016
1097
  return self.form_invalid(form)
1098
+ table = None
1017
1099
  if not delete_all:
1018
1100
  table_class = self.get_table_class()
1019
- table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
1101
+ table = table_class(queryset, orderable=False)
1020
1102
  if not table.rows:
1021
1103
  messages.warning(
1022
1104
  request,
@@ -1024,7 +1106,13 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
1024
1106
  )
1025
1107
  return redirect(self.get_return_url(request))
1026
1108
 
1027
- data.update({"table": table})
1109
+ data.update(
1110
+ {
1111
+ "table": table,
1112
+ "total_objs_to_delete": queryset.count(),
1113
+ "delete_all": delete_all,
1114
+ }
1115
+ )
1028
1116
  return Response(data)
1029
1117
 
1030
1118
 
@@ -1082,14 +1170,14 @@ class ObjectBulkCreateViewMixin(NautobotViewSetMixin): # 3.0 TODO: remove, unus
1082
1170
  return self.form_invalid(form)
1083
1171
 
1084
1172
 
1085
- class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, EditAndDeleteAllModelMixin):
1173
+ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, BulkEditAndBulkDeleteModelMixin):
1086
1174
  """
1087
1175
  UI mixin to bulk update model instances.
1088
1176
  """
1089
1177
 
1090
- filterset_class = None
1091
- bulk_update_form_class = None
1178
+ filterset_class: ClassVar[Optional[type[FilterSet]]] = None
1092
1179
 
1180
+ # NOTE: Performing BulkEdit Objects has been moved to a system job, but the logic remains here to ensure backward compatibility.
1093
1181
  def _process_bulk_update_form(self, form):
1094
1182
  request = self.request
1095
1183
  queryset = self.get_queryset()
@@ -1104,7 +1192,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
1104
1192
  for field in form.fields
1105
1193
  if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
1106
1194
  ]
1107
- nullified_fields = request.POST.getlist("_nullify")
1195
+ nullified_fields = request.POST.getlist("_nullify") or []
1108
1196
  with deferred_change_logging_for_bulk_operation():
1109
1197
  updated_objects = []
1110
1198
  edit_all = self.request.POST.get("_all")
@@ -1123,7 +1211,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
1123
1211
  # This form field is used to modify a field rather than set its value directly
1124
1212
  model_field = None
1125
1213
  # Handle nullification
1126
- if name in form.nullable_fields and name in nullified_fields:
1214
+ if name in form.nullable_fields and nullified_fields and name in nullified_fields:
1127
1215
  if isinstance(model_field, ManyToManyField):
1128
1216
  getattr(obj, name).set([])
1129
1217
  else:
@@ -1137,7 +1225,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
1137
1225
  setattr(obj, name, form.cleaned_data[name])
1138
1226
  # Update custom fields
1139
1227
  for field_name in form_custom_fields:
1140
- if field_name in form.nullable_fields and field_name in nullified_fields:
1228
+ if field_name in form.nullable_fields and nullified_fields and field_name in nullified_fields:
1141
1229
  obj.cf[remove_prefix_from_cf_key(field_name)] = None
1142
1230
  elif form.cleaned_data.get(field_name) not in (None, "", []):
1143
1231
  obj.cf[remove_prefix_from_cf_key(field_name)] = form.cleaned_data[field_name]
@@ -1200,7 +1288,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
1200
1288
  form = form_class(queryset.model, request.POST, edit_all=edit_all)
1201
1289
  restrict_form_fields(form, request.user)
1202
1290
  if form.is_valid():
1203
- return self.form_valid(form)
1291
+ return self.send_bulk_edit_objects_to_job(self.request, form, queryset.model)
1204
1292
  else:
1205
1293
  return self.form_invalid(form)
1206
1294
  table = None
@@ -244,12 +244,11 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
244
244
  restrict_form_fields(form, request.user)
245
245
  elif view.action == "bulk_destroy":
246
246
  pk_list = getattr(view, "pk_list", [])
247
- if pk_list:
248
- initial = {
249
- "pk": pk_list,
250
- "return_url": return_url,
251
- }
252
- form = form_class(initial=initial)
247
+ initial = {
248
+ "pk": pk_list,
249
+ "return_url": return_url,
250
+ }
251
+ form = form_class(initial=initial)
253
252
  delete_all = request.POST.get("_all")
254
253
  if not delete_all:
255
254
  table = self.construct_table(view, pk_list=pk_list)
@@ -347,6 +346,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
347
346
  # Ensure the proper inheritance of context variables is applied: the view's returned data takes priority over the viewset's get_extra_context
348
347
  context.update(view.get_extra_context(request, instance))
349
348
  context.update(self.get_template_context(data, renderer_context))
349
+
350
350
  return context
351
351
 
352
352
  def render(self, data, accepted_media_type=None, renderer_context=None):
@@ -247,15 +247,15 @@ class LocationSerializer(
247
247
  # https://www.django-rest-framework.org/api-guide/validators/#optional-fields
248
248
  validators = []
249
249
 
250
- def validate(self, data):
250
+ def validate(self, attrs):
251
251
  # Validate uniqueness of (parent, name) since we omitted the automatically created validator from Meta.
252
- if data.get("parent") and data.get("name"):
252
+ if attrs.get("parent") and attrs.get("name"):
253
253
  validator = UniqueTogetherValidator(queryset=Location.objects.all(), fields=("parent", "name"))
254
- validator(data, self)
254
+ validator(attrs, self)
255
255
 
256
- super().validate(data)
256
+ super().validate(attrs)
257
257
 
258
- return data
258
+ return attrs
259
259
 
260
260
 
261
261
  #
@@ -285,20 +285,20 @@ class RackSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
285
285
  # This prevents facility_id and rack_group from being interpreted as required fields.
286
286
  validators = []
287
287
 
288
- def validate(self, data):
288
+ def validate(self, attrs):
289
289
  # Validate uniqueness of (rack_group, name) since we omitted the automatically-created validator above.
290
- if data.get("rack_group", None):
290
+ if attrs.get("rack_group", None):
291
291
  validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "name"))
292
- validator(data, self)
292
+ validator(attrs, self)
293
293
  # Validate uniqueness of (rack_group, facility_id) since we omitted the automatically-created validator above.
294
- if data.get("facility_id", None) and data.get("rack_group", None):
294
+ if attrs.get("facility_id", None) and attrs.get("rack_group", None):
295
295
  validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "facility_id"))
296
- validator(data, self)
296
+ validator(attrs, self)
297
297
 
298
298
  # Enforce model validation
299
- super().validate(data)
299
+ super().validate(attrs)
300
300
 
301
- return data
301
+ return attrs
302
302
 
303
303
 
304
304
  class RackUnitSerializer(serializers.Serializer):
@@ -369,7 +369,7 @@ class ManufacturerSerializer(NautobotModelSerializer):
369
369
  fields = "__all__"
370
370
 
371
371
 
372
- class DeviceFamilySerializer(NautobotModelSerializer):
372
+ class DeviceFamilySerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
373
373
  device_type_count = serializers.IntegerField(read_only=True)
374
374
 
375
375
  class Meta:
@@ -534,14 +534,6 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
534
534
  "vc_priority": {"label": "Virtual chassis priority"},
535
535
  }
536
536
 
537
- def get_additional_detail_view_tabs(self):
538
- """Add "Virtual Chassis" as a separate detail tab."""
539
- tabs = super().get_additional_detail_view_tabs()
540
- tabs["Virtual Chassis"] = [
541
- {"Virtual Chassis": {"fields": ["virtual_chassis", "vc_position", "vc_priority"]}},
542
- ]
543
- return tabs
544
-
545
537
  def get_field_names(self, declared_fields, info):
546
538
  """
547
539
  Add a couple of special fields to the serializer.
@@ -558,20 +550,20 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
558
550
  def get_config_context(self, obj):
559
551
  return obj.get_config_context()
560
552
 
561
- def validate(self, data):
553
+ def validate(self, attrs):
562
554
  # Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
563
- if data.get("rack") and data.get("position") and data.get("face"):
555
+ if attrs.get("rack") and attrs.get("position") and attrs.get("face"):
564
556
  validator = UniqueTogetherValidator(
565
557
  queryset=Device.objects.all(),
566
558
  fields=("rack", "position", "face"),
567
559
  message=f"The position and face is already occupied on this rack. {UniqueTogetherValidator.message}",
568
560
  )
569
- validator(data, self)
561
+ validator(attrs, self)
570
562
 
571
563
  # Enforce model validation
572
- super().validate(data)
564
+ super().validate(attrs)
573
565
 
574
- return data
566
+ return attrs
575
567
 
576
568
 
577
569
  class DeviceNAPALMSerializer(serializers.Serializer):
@@ -644,22 +636,22 @@ class PowerPortSerializer(
644
636
 
645
637
 
646
638
  class InterfaceCommonSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
647
- def validate(self, data):
639
+ def validate(self, attrs):
648
640
  # Validate many-to-many VLAN assignments
649
- mode = data.get("mode", getattr(self.instance, "mode", None))
641
+ mode = attrs.get("mode", getattr(self.instance, "mode", None))
650
642
 
651
643
  if mode != InterfaceModeChoices.MODE_TAGGED:
652
- if data.get("tagged_vlans"):
644
+ if attrs.get("tagged_vlans"):
653
645
  raise serializers.ValidationError(
654
646
  {
655
647
  "tagged_vlans": f"Mode must be set to {InterfaceModeChoices.MODE_TAGGED} when specifying tagged_vlans"
656
648
  }
657
649
  )
658
650
 
659
- if data.get("tagged_vlans") != [] and self.instance and self.instance.tagged_vlans.exists():
651
+ if attrs.get("tagged_vlans") != [] and self.instance and self.instance.tagged_vlans.exists():
660
652
  raise serializers.ValidationError({"tagged_vlans": f"Clear tagged_vlans to set mode to {mode}"})
661
653
 
662
- return super().validate(data)
654
+ return super().validate(attrs)
663
655
 
664
656
 
665
657
  class InterfaceSerializer(
@@ -671,7 +663,7 @@ class InterfaceSerializer(
671
663
  type = ChoiceField(choices=InterfaceTypeChoices)
672
664
  mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
673
665
  mac_address = serializers.CharField(allow_blank=True, allow_null=True, required=False)
674
- ip_address_count = serializers.IntegerField(read_only=True)
666
+ ip_address_count = serializers.IntegerField(read_only=True, source="_ip_address_count")
675
667
 
676
668
  class Meta:
677
669
  model = Interface
@@ -760,18 +752,18 @@ class InventoryItemSerializer(TaggedModelSerializerMixin, TreeModelSerializerMix
760
752
  # https://www.django-rest-framework.org/api-guide/validators/#optional-fields
761
753
  validators = []
762
754
 
763
- def validate(self, data):
755
+ def validate(self, attrs):
764
756
  # Validate uniqueness of (device, parent, name) since we omitted the automatically created validator from Meta.
765
- if data.get("device") and data.get("parent") and data.get("name"):
757
+ if attrs.get("device") and attrs.get("parent") and attrs.get("name"):
766
758
  validator = UniqueTogetherValidator(
767
759
  queryset=InventoryItem.objects.all(),
768
760
  fields=("device", "parent", "name"),
769
761
  )
770
- validator(data, self)
762
+ validator(attrs, self)
771
763
 
772
- super().validate(data)
764
+ super().validate(attrs)
773
765
 
774
- return data
766
+ return attrs
775
767
 
776
768
 
777
769
  #
@@ -989,7 +981,7 @@ class DeviceTypeToSoftwareImageFileSerializer(ValidatedModelSerializer):
989
981
 
990
982
  class ControllerSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
991
983
  capabilities = serializers.ListField(
992
- child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True
984
+ child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True, required=False
993
985
  )
994
986
 
995
987
  class Meta:
@@ -999,7 +991,7 @@ class ControllerSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
999
991
 
1000
992
  class ControllerManagedDeviceGroupSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
1001
993
  capabilities = serializers.ListField(
1002
- child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True
994
+ child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True, required=False
1003
995
  )
1004
996
 
1005
997
  class Meta:
@@ -1018,21 +1010,21 @@ class ModuleBaySerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
1018
1010
  fields = "__all__"
1019
1011
  validators = []
1020
1012
 
1021
- def validate(self, data):
1013
+ def validate(self, attrs):
1022
1014
  """Validate device and module field constraints for module bay."""
1023
- if data.get("parent_device") and data.get("parent_module"):
1015
+ if attrs.get("parent_device") and attrs.get("parent_module"):
1024
1016
  raise serializers.ValidationError("Only one of parent_device or parent_module must be set")
1025
- if data.get("parent_device"):
1017
+ if attrs.get("parent_device"):
1026
1018
  validator = UniqueTogetherValidator(
1027
1019
  queryset=self.Meta.model.objects.all(), fields=("parent_device", "name")
1028
1020
  )
1029
- validator(data, self)
1030
- if data.get("parent_module"):
1021
+ validator(attrs, self)
1022
+ if attrs.get("parent_module"):
1031
1023
  validator = UniqueTogetherValidator(
1032
1024
  queryset=self.Meta.model.objects.all(), fields=("parent_module", "name")
1033
1025
  )
1034
- validator(data, self)
1035
- return super().validate(data)
1026
+ validator(attrs, self)
1027
+ return super().validate(attrs)
1036
1028
 
1037
1029
 
1038
1030
  class ModuleBayTemplateSerializer(NautobotModelSerializer):
@@ -1041,17 +1033,17 @@ class ModuleBayTemplateSerializer(NautobotModelSerializer):
1041
1033
  fields = "__all__"
1042
1034
  validators = []
1043
1035
 
1044
- def validate(self, data):
1036
+ def validate(self, attrs):
1045
1037
  """Validate device_type and module_type field constraints for module bay template."""
1046
- if data.get("device_type") and data.get("module_type"):
1038
+ if attrs.get("device_type") and attrs.get("module_type"):
1047
1039
  raise serializers.ValidationError("Only one of device_type or module_type must be set")
1048
- if data.get("device_type"):
1040
+ if attrs.get("device_type"):
1049
1041
  validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("device_type", "name"))
1050
- validator(data, self)
1051
- if data.get("module_type"):
1042
+ validator(attrs, self)
1043
+ if attrs.get("module_type"):
1052
1044
  validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("module_type", "name"))
1053
- validator(data, self)
1054
- return super().validate(data)
1045
+ validator(attrs, self)
1046
+ return super().validate(attrs)
1055
1047
 
1056
1048
 
1057
1049
  class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
@@ -1061,17 +1053,17 @@ class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
1061
1053
 
1062
1054
  validators = []
1063
1055
 
1064
- def validate(self, data):
1056
+ def validate(self, attrs):
1065
1057
  """Validate asset_Tag, serial, parent_module_bay and location field constraints for module."""
1066
- if data.get("parent_module_bay") and data.get("location"):
1058
+ if attrs.get("parent_module_bay") and attrs.get("location"):
1067
1059
  raise serializers.ValidationError("Only one of parent_module_bay or location must be set")
1068
- if data.get("serial"):
1060
+ if attrs.get("serial"):
1069
1061
  validator = UniqueTogetherValidator(queryset=Module.objects.all(), fields=("module_type", "serial"))
1070
- validator(data, self)
1071
- if data.get("asset_tag"):
1062
+ validator(attrs, self)
1063
+ if attrs.get("asset_tag"):
1072
1064
  validator = UniqueValidator(queryset=Module.objects.all())
1073
- validator(data["asset_tag"], self.fields["asset_tag"])
1074
- return super().validate(data)
1065
+ validator(attrs["asset_tag"], self.fields["asset_tag"])
1066
+ return super().validate(attrs)
1075
1067
 
1076
1068
 
1077
1069
  class ModuleTypeSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
@@ -1080,16 +1072,16 @@ class ModuleTypeSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
1080
1072
  fields = "__all__"
1081
1073
 
1082
1074
 
1083
- class VirtualDeviceContextSerializer(NautobotModelSerializer):
1075
+ class VirtualDeviceContextSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
1084
1076
  class Meta:
1085
1077
  model = VirtualDeviceContext
1086
1078
  fields = "__all__"
1087
1079
 
1088
- def validate(self, data):
1080
+ def validate(self, attrs):
1089
1081
  """Validate device cannot be changed for VirtualDeviceContext."""
1090
- if data.get("device") and self.instance and self.instance.device != data.get("device"):
1082
+ if attrs.get("device") and self.instance and self.instance.device != attrs.get("device"):
1091
1083
  raise serializers.ValidationError("Changing the device of a VirtualDeviceContext is not allowed.")
1092
- return super().validate(data)
1084
+ return super().validate(attrs)
1093
1085
 
1094
1086
 
1095
1087
  class InterfaceVDCAssignmentSerializer(ValidatedModelSerializer):