nautobot 2.4.0b1__py3-none-any.whl → 2.4.2__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 (668) 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/tests/integration/test_circuits_bulk_operations.py +43 -0
  22. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  23. nautobot/circuits/views.py +3 -3
  24. nautobot/cloud/api/views.py +6 -10
  25. nautobot/cloud/models.py +1 -1
  26. nautobot/cloud/views.py +0 -16
  27. nautobot/core/api/constants.py +11 -0
  28. nautobot/core/api/fields.py +5 -5
  29. nautobot/core/api/filter_backends.py +3 -9
  30. nautobot/core/api/schema.py +13 -2
  31. nautobot/core/api/serializers.py +40 -34
  32. nautobot/core/api/views.py +56 -4
  33. nautobot/core/apps/__init__.py +0 -5
  34. nautobot/core/celery/log.py +4 -4
  35. nautobot/core/celery/schedulers.py +2 -2
  36. nautobot/core/choices.py +2 -2
  37. nautobot/core/events/__init__.py +3 -3
  38. nautobot/core/filters.py +67 -35
  39. nautobot/core/forms/__init__.py +19 -19
  40. nautobot/core/forms/fields.py +14 -11
  41. nautobot/core/forms/forms.py +33 -2
  42. nautobot/core/graphql/types.py +1 -1
  43. nautobot/core/jobs/__init__.py +28 -7
  44. nautobot/core/jobs/bulk_actions.py +285 -0
  45. nautobot/core/jobs/cleanup.py +48 -12
  46. nautobot/core/jobs/groups.py +1 -1
  47. nautobot/core/management/commands/validate_models.py +1 -1
  48. nautobot/core/models/__init__.py +3 -1
  49. nautobot/core/models/query_functions.py +2 -2
  50. nautobot/core/models/tree_queries.py +6 -3
  51. nautobot/core/settings.py +29 -2
  52. nautobot/core/settings.yaml +21 -0
  53. nautobot/core/tables.py +79 -61
  54. nautobot/core/templates/about.html +67 -0
  55. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  56. nautobot/core/templates/inc/media.html +3 -0
  57. nautobot/core/templates/inc/nav_menu.html +1 -0
  58. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  59. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  60. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  61. nautobot/core/templates/search.html +7 -0
  62. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  63. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  64. nautobot/core/templates/utilities/theme_preview.html +7 -0
  65. nautobot/core/templatetags/helpers.py +11 -2
  66. nautobot/core/testing/__init__.py +8 -8
  67. nautobot/core/testing/api.py +170 -15
  68. nautobot/core/testing/filters.py +45 -10
  69. nautobot/core/testing/forms.py +2 -0
  70. nautobot/core/testing/integration.py +514 -5
  71. nautobot/core/testing/mixins.py +7 -2
  72. nautobot/core/testing/views.py +44 -29
  73. nautobot/core/tests/integration/test_app_home.py +0 -1
  74. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  75. nautobot/core/tests/integration/test_filters.py +0 -2
  76. nautobot/core/tests/integration/test_home.py +0 -1
  77. nautobot/core/tests/integration/test_navbar.py +0 -1
  78. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  79. nautobot/core/tests/runner.py +1 -1
  80. nautobot/core/tests/test_api.py +98 -1
  81. nautobot/core/tests/test_csv.py +25 -3
  82. nautobot/core/tests/test_filters.py +209 -246
  83. nautobot/core/tests/test_forms.py +1 -0
  84. nautobot/core/tests/test_jobs.py +492 -1
  85. nautobot/core/tests/test_models.py +9 -0
  86. nautobot/core/tests/test_settings_schema.py +7 -0
  87. nautobot/core/tests/test_tables.py +100 -0
  88. nautobot/core/tests/test_utils.py +63 -1
  89. nautobot/core/tests/test_views.py +30 -3
  90. nautobot/core/ui/nav.py +1 -0
  91. nautobot/core/ui/object_detail.py +15 -1
  92. nautobot/core/urls.py +11 -0
  93. nautobot/core/utils/git.py +7 -2
  94. nautobot/core/utils/lookup.py +11 -8
  95. nautobot/core/utils/querysets.py +64 -0
  96. nautobot/core/utils/requests.py +24 -9
  97. nautobot/core/views/__init__.py +42 -0
  98. nautobot/core/views/generic.py +131 -197
  99. nautobot/core/views/mixins.py +136 -41
  100. nautobot/core/views/renderers.py +6 -6
  101. nautobot/core/views/utils.py +2 -2
  102. nautobot/dcim/api/serializers.py +56 -64
  103. nautobot/dcim/api/views.py +47 -113
  104. nautobot/dcim/constants.py +6 -13
  105. nautobot/dcim/factory.py +6 -1
  106. nautobot/dcim/filters/__init__.py +31 -2
  107. nautobot/dcim/forms.py +48 -17
  108. nautobot/dcim/graphql/types.py +2 -2
  109. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  110. nautobot/dcim/models/__init__.py +1 -1
  111. nautobot/dcim/models/device_component_templates.py +2 -2
  112. nautobot/dcim/models/device_components.py +22 -20
  113. nautobot/dcim/models/devices.py +10 -1
  114. nautobot/dcim/models/locations.py +3 -3
  115. nautobot/dcim/models/power.py +6 -5
  116. nautobot/dcim/models/racks.py +4 -4
  117. nautobot/dcim/tables/__init__.py +3 -3
  118. nautobot/dcim/tables/devices.py +9 -6
  119. nautobot/dcim/tables/devicetypes.py +2 -2
  120. nautobot/dcim/tables/racks.py +1 -1
  121. nautobot/dcim/templates/dcim/cable.html +1 -1
  122. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  123. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  124. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  125. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  126. nautobot/dcim/templates/dcim/device/base.html +1 -1
  127. nautobot/dcim/templates/dcim/device.html +3 -11
  128. nautobot/dcim/templates/dcim/device_component.html +1 -1
  129. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  130. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  131. nautobot/dcim/templates/dcim/location.html +2 -10
  132. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  133. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  134. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  135. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  136. nautobot/dcim/templates/dcim/platform.html +1 -1
  137. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  138. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  139. nautobot/dcim/templates/dcim/rack.html +2 -10
  140. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  141. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  142. nautobot/dcim/templates/dcim/rackreservation.html +3 -11
  143. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  144. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  145. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  146. nautobot/dcim/tests/integration/test_controller.py +62 -0
  147. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  148. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  149. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  150. nautobot/dcim/tests/test_api.py +16 -5
  151. nautobot/dcim/tests/test_filters.py +33 -0
  152. nautobot/dcim/tests/test_forms.py +51 -2
  153. nautobot/dcim/tests/test_graphql.py +52 -0
  154. nautobot/dcim/tests/test_jobs.py +118 -0
  155. nautobot/dcim/tests/test_models.py +52 -9
  156. nautobot/dcim/tests/test_views.py +30 -84
  157. nautobot/dcim/views.py +13 -28
  158. nautobot/extras/api/customfields.py +2 -2
  159. nautobot/extras/api/serializers.py +123 -85
  160. nautobot/extras/api/views.py +33 -30
  161. nautobot/extras/constants.py +3 -0
  162. nautobot/extras/datasources/git.py +125 -0
  163. nautobot/extras/filters/__init__.py +8 -6
  164. nautobot/extras/forms/base.py +2 -2
  165. nautobot/extras/forms/forms.py +139 -31
  166. nautobot/extras/forms/mixins.py +14 -6
  167. nautobot/extras/group_sync.py +3 -3
  168. nautobot/extras/health_checks.py +1 -2
  169. nautobot/extras/jobs.py +85 -18
  170. nautobot/extras/managers.py +3 -1
  171. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  172. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  173. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  174. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  175. nautobot/extras/models/__init__.py +1 -1
  176. nautobot/extras/models/contacts.py +1 -1
  177. nautobot/extras/models/customfields.py +41 -23
  178. nautobot/extras/models/datasources.py +85 -0
  179. nautobot/extras/models/groups.py +11 -9
  180. nautobot/extras/models/jobs.py +23 -4
  181. nautobot/extras/models/models.py +17 -2
  182. nautobot/extras/models/relationships.py +17 -5
  183. nautobot/extras/plugins/__init__.py +13 -2
  184. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  185. nautobot/extras/plugins/tables.py +16 -14
  186. nautobot/extras/plugins/views.py +65 -69
  187. nautobot/extras/registry.py +1 -1
  188. nautobot/extras/secrets/__init__.py +2 -2
  189. nautobot/extras/signals.py +15 -1
  190. nautobot/extras/tables.py +7 -5
  191. nautobot/extras/templates/extras/computedfield.html +1 -1
  192. nautobot/extras/templates/extras/configcontext.html +1 -1
  193. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  194. nautobot/extras/templates/extras/customfield.html +1 -1
  195. nautobot/extras/templates/extras/customlink.html +1 -1
  196. nautobot/extras/templates/extras/dynamicgroup.html +2 -10
  197. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  198. nautobot/extras/templates/extras/gitrepository.html +1 -1
  199. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  200. nautobot/extras/templates/extras/job_detail.html +17 -1
  201. nautobot/extras/templates/extras/job_edit.html +1 -0
  202. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  203. nautobot/extras/templates/extras/jobhook.html +1 -1
  204. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  205. nautobot/extras/templates/extras/jobresult.html +1 -1
  206. nautobot/extras/templates/extras/marketplace.html +29 -11
  207. nautobot/extras/templates/extras/objectchange.html +1 -1
  208. nautobot/extras/templates/extras/plugin_detail.html +33 -16
  209. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  210. nautobot/extras/templates/extras/relationship.html +1 -63
  211. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  212. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  213. nautobot/extras/templates/extras/secret.html +1 -1
  214. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  215. nautobot/extras/templates/extras/status.html +1 -1
  216. nautobot/extras/templates/extras/tag.html +1 -1
  217. nautobot/extras/templates/extras/webhook.html +1 -1
  218. nautobot/extras/templatetags/job_buttons.py +4 -4
  219. nautobot/extras/test_jobs/api_test_job.py +1 -1
  220. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  221. nautobot/extras/test_jobs/dry_run.py +1 -1
  222. nautobot/extras/test_jobs/fail.py +5 -5
  223. nautobot/extras/test_jobs/file_output.py +1 -1
  224. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  225. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  226. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  227. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  228. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  229. nautobot/extras/test_jobs/log_redaction.py +1 -1
  230. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  231. nautobot/extras/test_jobs/modify_db.py +1 -1
  232. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  233. nautobot/extras/test_jobs/object_var_required.py +1 -1
  234. nautobot/extras/test_jobs/object_vars.py +1 -1
  235. nautobot/extras/test_jobs/pass.py +3 -3
  236. nautobot/extras/test_jobs/profiling.py +1 -1
  237. nautobot/extras/test_jobs/relative_import.py +3 -3
  238. nautobot/extras/test_jobs/singleton.py +16 -0
  239. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  240. nautobot/extras/test_jobs/task_queues.py +1 -1
  241. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  242. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  243. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  244. nautobot/extras/tests/git_helper.py +9 -1
  245. nautobot/extras/tests/integration/__init__.py +29 -16
  246. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  247. nautobot/extras/tests/test_api.py +19 -13
  248. nautobot/extras/tests/test_customfields.py +50 -52
  249. nautobot/extras/tests/test_datasources.py +29 -1
  250. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  251. nautobot/extras/tests/test_filters.py +6 -6
  252. nautobot/extras/tests/test_forms.py +33 -1
  253. nautobot/extras/tests/test_jobs.py +178 -32
  254. nautobot/extras/tests/test_models.py +299 -10
  255. nautobot/extras/tests/test_plugins.py +62 -9
  256. nautobot/extras/tests/test_relationships.py +120 -9
  257. nautobot/extras/tests/test_utils.py +22 -1
  258. nautobot/extras/tests/test_views.py +56 -194
  259. nautobot/extras/utils.py +20 -10
  260. nautobot/extras/views.py +85 -110
  261. nautobot/ipam/api/fields.py +3 -3
  262. nautobot/ipam/api/serializers.py +41 -33
  263. nautobot/ipam/api/views.py +68 -117
  264. nautobot/ipam/factory.py +1 -1
  265. nautobot/ipam/filters.py +3 -2
  266. nautobot/ipam/lookups.py +101 -62
  267. nautobot/ipam/models.py +74 -18
  268. nautobot/ipam/querysets.py +2 -2
  269. nautobot/ipam/tables.py +25 -9
  270. nautobot/ipam/templates/ipam/ipaddress.html +2 -10
  271. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  272. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  273. nautobot/ipam/templates/ipam/prefix.html +2 -10
  274. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  275. nautobot/ipam/templates/ipam/rir.html +1 -1
  276. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  277. nautobot/ipam/templates/ipam/service.html +1 -1
  278. nautobot/ipam/templates/ipam/vlan.html +2 -10
  279. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  280. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  281. nautobot/ipam/templates/ipam/vrf.html +1 -1
  282. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  283. nautobot/ipam/tests/test_api.py +436 -3
  284. nautobot/ipam/tests/test_forms.py +49 -47
  285. nautobot/ipam/tests/test_migrations.py +30 -30
  286. nautobot/ipam/tests/test_models.py +119 -34
  287. nautobot/ipam/tests/test_querysets.py +63 -1
  288. nautobot/ipam/tests/test_utils.py +41 -2
  289. nautobot/ipam/tests/test_views.py +3 -0
  290. nautobot/ipam/utils/__init__.py +54 -17
  291. nautobot/ipam/views.py +61 -87
  292. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  293. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  294. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  295. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  296. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  297. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  298. nautobot/project-static/docs/404.html +131 -14
  299. nautobot/project-static/docs/apps/index.html +131 -14
  300. nautobot/project-static/docs/apps/nautobot-apps.html +132 -16
  301. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  302. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.60a45f97.min.js} +2 -2
  303. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.60a45f97.min.js.map} +2 -2
  304. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  305. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  306. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  307. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +147 -20
  308. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +144 -17
  309. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +459 -132
  310. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +175 -28
  311. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +180 -31
  312. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +138 -16
  313. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +137 -15
  314. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +164 -27
  315. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +187 -38
  316. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +193 -31
  317. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +216 -48
  318. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +324 -75
  319. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +666 -175
  320. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +194 -46
  321. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +538 -177
  322. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +578 -221
  323. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +145 -18
  324. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +156 -25
  325. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +492 -65
  326. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +705 -215
  327. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +943 -422
  328. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +144 -17
  329. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +619 -200
  330. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +474 -159
  331. nautobot/project-static/docs/development/apps/api/configuration-view.html +131 -14
  332. nautobot/project-static/docs/development/apps/api/database-backend-config.html +131 -14
  333. nautobot/project-static/docs/development/apps/api/models/django-admin.html +131 -14
  334. nautobot/project-static/docs/development/apps/api/models/global-search.html +131 -14
  335. nautobot/project-static/docs/development/apps/api/models/graphql.html +131 -14
  336. nautobot/project-static/docs/development/apps/api/models/index.html +131 -14
  337. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +131 -14
  338. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +131 -14
  339. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +131 -14
  340. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +131 -14
  341. nautobot/project-static/docs/development/apps/api/platform-features/index.html +131 -14
  342. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +131 -14
  343. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +131 -14
  344. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +131 -14
  345. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +131 -14
  346. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +153 -17
  347. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +131 -14
  348. nautobot/project-static/docs/development/apps/api/prometheus.html +131 -14
  349. nautobot/project-static/docs/development/apps/api/setup.html +131 -14
  350. nautobot/project-static/docs/development/apps/api/testing.html +131 -14
  351. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +131 -14
  352. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +131 -14
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +131 -14
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +131 -14
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +131 -14
  356. nautobot/project-static/docs/development/apps/api/views/base-template.html +131 -14
  357. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +131 -14
  358. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +131 -14
  359. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +131 -14
  360. nautobot/project-static/docs/development/apps/api/views/index.html +131 -14
  361. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +131 -14
  362. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +131 -14
  363. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +131 -14
  364. nautobot/project-static/docs/development/apps/api/views/notes.html +131 -14
  365. nautobot/project-static/docs/development/apps/api/views/rest-api.html +137 -16
  366. nautobot/project-static/docs/development/apps/api/views/urls.html +131 -14
  367. nautobot/project-static/docs/development/apps/index.html +131 -14
  368. nautobot/project-static/docs/development/apps/migration/code-updates.html +131 -14
  369. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +131 -14
  370. nautobot/project-static/docs/development/apps/migration/from-v1.html +131 -14
  371. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +131 -14
  372. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +131 -14
  373. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +131 -14
  374. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +131 -14
  375. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +135 -18
  376. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +131 -14
  377. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +297 -25
  378. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +131 -14
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +131 -14
  380. nautobot/project-static/docs/development/core/application-registry.html +131 -14
  381. nautobot/project-static/docs/development/core/best-practices.html +131 -14
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +131 -14
  383. nautobot/project-static/docs/development/core/caching.html +131 -14
  384. nautobot/project-static/docs/development/core/controllers.html +131 -14
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +158 -84
  386. nautobot/project-static/docs/development/core/generic-views.html +131 -14
  387. nautobot/project-static/docs/development/core/getting-started.html +334 -234
  388. nautobot/project-static/docs/development/core/homepage.html +134 -17
  389. nautobot/project-static/docs/development/core/index.html +131 -14
  390. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9829 -0
  391. nautobot/project-static/docs/development/core/model-checklist.html +141 -22
  392. nautobot/project-static/docs/development/core/model-features.html +131 -14
  393. nautobot/project-static/docs/development/core/natural-keys.html +131 -14
  394. nautobot/project-static/docs/development/core/navigation-menu.html +131 -14
  395. nautobot/project-static/docs/development/core/release-checklist.html +134 -17
  396. nautobot/project-static/docs/development/core/role-internals.html +131 -14
  397. nautobot/project-static/docs/development/core/settings.html +131 -14
  398. nautobot/project-static/docs/development/core/style-guide.html +134 -17
  399. nautobot/project-static/docs/development/core/templates.html +132 -15
  400. nautobot/project-static/docs/development/core/testing.html +131 -14
  401. nautobot/project-static/docs/development/core/ui-component-framework.html +454 -283
  402. nautobot/project-static/docs/development/core/user-preferences.html +131 -14
  403. nautobot/project-static/docs/development/index.html +131 -14
  404. nautobot/project-static/docs/development/jobs/index.html +301 -132
  405. nautobot/project-static/docs/development/jobs/migration/from-v1.html +131 -14
  406. nautobot/project-static/docs/index.html +139 -33
  407. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  408. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  409. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  410. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  411. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  412. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  413. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  414. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  415. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  416. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  417. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  418. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  419. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  420. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  421. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  422. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  423. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  424. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  425. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  426. nautobot/project-static/docs/objects.inv +0 -0
  427. nautobot/project-static/docs/overview/application_stack.html +132 -17
  428. nautobot/project-static/docs/overview/design_philosophy.html +131 -14
  429. nautobot/project-static/docs/release-notes/index.html +137 -22
  430. nautobot/project-static/docs/release-notes/version-1.0.html +319 -203
  431. nautobot/project-static/docs/release-notes/version-1.1.html +316 -200
  432. nautobot/project-static/docs/release-notes/version-1.2.html +391 -275
  433. nautobot/project-static/docs/release-notes/version-1.3.html +417 -301
  434. nautobot/project-static/docs/release-notes/version-1.4.html +502 -387
  435. nautobot/project-static/docs/release-notes/version-1.5.html +690 -576
  436. nautobot/project-static/docs/release-notes/version-1.6.html +989 -457
  437. nautobot/project-static/docs/release-notes/version-2.0.html +613 -499
  438. nautobot/project-static/docs/release-notes/version-2.1.html +448 -334
  439. nautobot/project-static/docs/release-notes/version-2.2.html +441 -327
  440. nautobot/project-static/docs/release-notes/version-2.3.html +1171 -451
  441. nautobot/project-static/docs/release-notes/version-2.4.html +800 -111
  442. nautobot/project-static/docs/requirements.txt +2 -2
  443. nautobot/project-static/docs/search/search_index.json +1 -1
  444. nautobot/project-static/docs/sitemap.xml +303 -287
  445. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +131 -14
  447. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +131 -14
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +133 -16
  449. nautobot/project-static/docs/user-guide/administration/configuration/index.html +131 -14
  450. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +131 -14
  451. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +195 -18
  452. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +131 -14
  453. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +134 -17
  454. nautobot/project-static/docs/user-guide/administration/guides/docker.html +131 -14
  455. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +131 -14
  456. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +131 -14
  457. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -14
  458. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +131 -14
  459. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +131 -14
  460. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +133 -16
  461. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +131 -14
  462. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +131 -14
  463. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +131 -14
  464. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +151 -18
  465. nautobot/project-static/docs/user-guide/administration/installation/index.html +131 -14
  466. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +132 -15
  467. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +131 -14
  468. nautobot/project-static/docs/user-guide/administration/installation/services.html +131 -14
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +131 -14
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +131 -14
  471. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  472. nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
  473. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +131 -14
  474. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +131 -14
  475. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +131 -14
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +131 -14
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +131 -14
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +131 -14
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +131 -14
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +134 -18
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +131 -14
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +131 -14
  483. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +131 -14
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +131 -14
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +131 -14
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +131 -14
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +131 -14
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +131 -14
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +131 -14
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +131 -14
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +131 -14
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +131 -14
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +131 -14
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +131 -14
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +131 -14
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +131 -14
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +131 -14
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +131 -14
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +131 -14
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +131 -14
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +131 -14
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +131 -14
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +131 -14
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +131 -14
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +131 -14
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +131 -14
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +131 -14
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +131 -14
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +131 -14
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +131 -14
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +131 -14
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +131 -14
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +131 -14
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +131 -14
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +131 -14
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +131 -14
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +131 -14
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +131 -14
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +131 -14
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +131 -14
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +131 -14
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +131 -14
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +131 -14
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +131 -14
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +131 -14
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +131 -14
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +131 -14
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +131 -14
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +131 -14
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +131 -14
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +131 -14
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +131 -14
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +131 -14
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +131 -14
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +131 -14
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +131 -14
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +131 -14
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +135 -22
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +131 -14
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +131 -14
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +131 -14
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +131 -14
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +131 -14
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +131 -14
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +131 -14
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +131 -14
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +131 -14
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +131 -14
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +131 -14
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +131 -14
  551. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +134 -17
  552. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +131 -14
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +131 -14
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +131 -14
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +131 -14
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +131 -14
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +131 -14
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +131 -14
  559. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +131 -14
  560. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +131 -14
  561. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +131 -14
  562. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +131 -14
  563. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +131 -14
  564. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +131 -14
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +131 -14
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +131 -14
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +134 -17
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +131 -14
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +131 -14
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +131 -14
  571. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +131 -14
  572. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +131 -14
  573. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +131 -14
  574. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +236 -34
  575. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +131 -14
  576. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +131 -14
  590. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +131 -14
  591. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +134 -17
  592. nautobot/project-static/docs/{development/core/local-k8s.html → user-guide/feature-guides/wireless-networks-and-controllers.html} +632 -566
  593. nautobot/project-static/docs/user-guide/index.html +131 -14
  594. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +131 -14
  595. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +135 -18
  596. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +131 -14
  597. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +131 -14
  598. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +131 -14
  599. nautobot/project-static/docs/user-guide/platform-functionality/events.html +131 -14
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +131 -14
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +131 -14
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +451 -16
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +131 -14
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +131 -14
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +131 -14
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +135 -17
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +131 -14
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +134 -17
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +131 -14
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +131 -14
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9797 -0
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +131 -14
  613. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +131 -14
  614. nautobot/project-static/docs/user-guide/platform-functionality/note.html +131 -14
  615. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +131 -14
  616. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +132 -15
  617. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +131 -14
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +131 -14
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +179 -35
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +159 -15
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +131 -14
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +131 -14
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +131 -14
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +131 -14
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +131 -14
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +131 -14
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +131 -14
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +131 -14
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +131 -14
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +131 -14
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +131 -14
  632. nautobot/project-static/js/forms.js +1 -1
  633. nautobot/tenancy/api/views.py +9 -13
  634. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  635. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  636. nautobot/tenancy/views.py +4 -2
  637. nautobot/users/admin.py +1 -1
  638. nautobot/users/api/serializers.py +5 -4
  639. nautobot/users/api/views.py +3 -3
  640. nautobot/virtualization/api/serializers.py +4 -4
  641. nautobot/virtualization/api/views.py +5 -24
  642. nautobot/virtualization/filters.py +20 -3
  643. nautobot/virtualization/models.py +1 -1
  644. nautobot/virtualization/tables.py +2 -2
  645. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  646. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  648. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -10
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  651. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  652. nautobot/virtualization/tests/test_filters.py +17 -0
  653. nautobot/wireless/filters.py +2 -2
  654. nautobot/wireless/forms.py +1 -1
  655. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  656. nautobot/wireless/tests/integration/__init__.py +0 -0
  657. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  658. nautobot/wireless/tests/test_filters.py +29 -1
  659. nautobot/wireless/tests/test_views.py +22 -1
  660. nautobot/wireless/views.py +0 -10
  661. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/METADATA +9 -9
  662. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/RECORD +667 -610
  663. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +1 -1
  664. nautobot/core/fixtures/user-data.json +0 -59
  665. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  666. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
  667. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
  668. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from copy import deepcopy
2
2
  import logging
3
3
  import re
4
+ from typing import ClassVar, Optional
4
5
 
5
6
  from django.conf import settings
6
7
  from django.contrib import messages
@@ -8,12 +9,11 @@ from django.contrib.auth.mixins import LoginRequiredMixin
8
9
  from django.contrib.auth.models import AnonymousUser
9
10
  from django.contrib.contenttypes.models import ContentType
10
11
  from django.core.exceptions import (
11
- FieldDoesNotExist,
12
12
  ObjectDoesNotExist,
13
13
  ValidationError,
14
14
  )
15
15
  from django.db import IntegrityError, transaction
16
- from django.db.models import ManyToManyField, ProtectedError, Q
16
+ from django.db.models import Model, ProtectedError, Q, QuerySet
17
17
  from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
18
18
  from django.http import HttpResponse
19
19
  from django.shortcuts import get_object_or_404, redirect, render
@@ -22,7 +22,8 @@ from django.utils.encoding import iri_to_uri
22
22
  from django.utils.html import format_html
23
23
  from django.utils.http import url_has_allowed_host_and_scheme
24
24
  from django.views.generic import View
25
- from django_tables2 import RequestConfig
25
+ from django_filters import FilterSet
26
+ from django_tables2 import RequestConfig, Table
26
27
 
27
28
  from nautobot.core.api.utils import get_serializer_for_model
28
29
  from nautobot.core.constants import MAX_PAGE_SIZE_DEFAULT
@@ -47,7 +48,7 @@ from nautobot.core.utils.requests import (
47
48
  get_filterable_params_from_filter_params,
48
49
  normalize_querydict,
49
50
  )
50
- from nautobot.core.views.mixins import EditAndDeleteAllModelMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin
51
+ from nautobot.core.views.mixins import BulkEditAndBulkDeleteModelMixin, GetReturnURLMixin, ObjectPermissionRequiredMixin
51
52
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
52
53
  from nautobot.core.views.utils import (
53
54
  check_filter_for_display,
@@ -58,9 +59,7 @@ from nautobot.core.views.utils import (
58
59
  prepare_cloned_fields,
59
60
  view_changes_not_saved,
60
61
  )
61
- from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
62
62
  from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
63
- from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, remove_prefix_from_cf_key
64
63
 
65
64
 
66
65
  class GenericView(LoginRequiredMixin, View):
@@ -79,8 +78,8 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
79
78
  template_name: Name of the template to use
80
79
  """
81
80
 
82
- queryset = None
83
- template_name = None
81
+ queryset: ClassVar[Optional[QuerySet]] = None # TODO: required, declared Optional only to avoid breaking change
82
+ template_name: ClassVar[Optional[str]] = None
84
83
  object_detail_content = None
85
84
 
86
85
  def get_required_permission(self):
@@ -142,10 +141,10 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
142
141
  non_filter_params: List of query parameters that are **not** used for queryset filtering
143
142
  """
144
143
 
145
- queryset = None
146
- filterset = None
147
- filterset_form = None
148
- table = None
144
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
145
+ filterset: Optional[type[FilterSet]] = None
146
+ filterset_form: Optional[type[Form]] = None
147
+ table: Optional[type[Table]] = None
149
148
  template_name = "generic/object_list.html"
150
149
  action_buttons = ("add", "import", "export")
151
150
  non_filter_params = (
@@ -162,7 +161,11 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
162
161
  def get_filter_params(self, request):
163
162
  """Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
164
163
  params = request.GET.copy()
165
- filter_params = get_filterable_params_from_filter_params(params, self.non_filter_params, self.filterset())
164
+ filter_params = get_filterable_params_from_filter_params(
165
+ params,
166
+ self.non_filter_params,
167
+ self.filterset(), # pylint: disable=not-callable # this fn is only called if filterset is not None
168
+ )
166
169
  if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
167
170
  return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
168
171
  return filter_params
@@ -236,9 +239,9 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
236
239
  except ObjectDoesNotExist:
237
240
  pass
238
241
 
239
- if self.filterset:
242
+ if self.filterset is not None:
240
243
  filter_params = self.get_filter_params(request)
241
- filterset = self.filterset(filter_params, self.queryset)
244
+ filterset = self.filterset(filter_params, self.queryset) # pylint:disable=not-callable # only if not None
242
245
  self.queryset = filterset.qs
243
246
  if not filterset.is_valid():
244
247
  messages.error(
@@ -266,7 +269,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
266
269
  else:
267
270
  dynamic_filter_form = DynamicFilterFormSet(filterset=filterset)
268
271
 
269
- if self.filterset_form:
272
+ if self.filterset_form is not None:
270
273
  filter_form = self.filterset_form(filter_params, label_suffix="")
271
274
 
272
275
  # Check for export template rendering
@@ -326,14 +329,14 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
326
329
  messages.error(request, f"Saved view {current_saved_view_pk} not found")
327
330
 
328
331
  # Construct the objects table
329
- if self.table:
332
+ if self.table is not None:
330
333
  if self.request.GET.getlist("sort") or (
331
334
  current_saved_view is not None and current_saved_view.config.get("sort_order")
332
335
  ):
333
336
  hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
334
337
  table_changes_pending = self.request.GET.get("table_changes_pending", False)
335
338
 
336
- table = self.table(
339
+ table = self.table( # pylint: disable=not-callable # we confirmed that self.table is not None
337
340
  self.queryset,
338
341
  table_changes_pending=table_changes_pending,
339
342
  saved_view=current_saved_view,
@@ -410,8 +413,8 @@ class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
410
413
  template_name: The name of the template
411
414
  """
412
415
 
413
- queryset = None
414
- model_form = None
416
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
417
+ model_form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
415
418
  template_name = "generic/object_create.html"
416
419
 
417
420
  def get_required_permission(self):
@@ -455,7 +458,9 @@ class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
455
458
  obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
456
459
 
457
460
  initial_data = normalize_querydict(request.GET, form_class=self.model_form)
458
- form = self.model_form(instance=obj, initial=initial_data)
461
+ if self.model_form is None:
462
+ raise RuntimeError("self.model_form must not be None")
463
+ form = self.model_form(instance=obj, initial=initial_data) # pylint: disable=not-callable
459
464
  restrict_form_fields(form, request.user)
460
465
 
461
466
  return render(
@@ -485,7 +490,9 @@ class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
485
490
  def post(self, request, *args, **kwargs):
486
491
  logger = logging.getLogger(__name__ + ".ObjectEditView")
487
492
  obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
488
- form = self.model_form(
493
+ if self.model_form is None:
494
+ raise RuntimeError("self.model_form must not be None")
495
+ form = self.model_form( # pylint: disable=not-callable
489
496
  data=request.POST,
490
497
  files=request.FILES,
491
498
  initial=normalize_querydict(request.GET, form_class=self.model_form),
@@ -553,7 +560,7 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
553
560
  template_name: The name of the template
554
561
  """
555
562
 
556
- queryset = None
563
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
557
564
  template_name = "generic/object_delete.html"
558
565
 
559
566
  def get_required_permission(self):
@@ -633,9 +640,9 @@ class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
633
640
  template_name: The name of the template
634
641
  """
635
642
 
636
- queryset = None
637
- form = None
638
- model_form = None
643
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
644
+ form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
645
+ model_form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
639
646
  pattern_target = ""
640
647
  template_name = None
641
648
 
@@ -645,12 +652,14 @@ class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
645
652
  def get(self, request):
646
653
  # Set initial values for visible form fields from query args
647
654
  initial = {}
655
+ if self.form is None or self.model_form is None:
656
+ raise RuntimeError("self.form and self.model_form must not be None")
648
657
  for field in getattr(self.model_form._meta, "fields", []):
649
658
  if request.GET.get(field):
650
659
  initial[field] = request.GET[field]
651
660
 
652
- form = self.form()
653
- model_form = self.model_form(initial=initial)
661
+ form = self.form() # pylint: disable=not-callable
662
+ model_form = self.model_form(initial=initial) # pylint: disable=not-callable
654
663
 
655
664
  return render(
656
665
  request,
@@ -665,9 +674,11 @@ class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
665
674
 
666
675
  def post(self, request):
667
676
  logger = logging.getLogger(__name__ + ".BulkCreateView")
677
+ if self.queryset is None or self.form is None or self.model_form is None:
678
+ raise RuntimeError("self.queryset, self.form, and self.model_form must not be None")
668
679
  model = self.queryset.model
669
- form = self.form(request.POST)
670
- model_form = self.model_form(request.POST)
680
+ form = self.form(request.POST) # pylint: disable=not-callable
681
+ model_form = self.model_form(request.POST) # pylint: disable=not-callable
671
682
 
672
683
  if form.is_valid():
673
684
  logger.debug("Form validation was successful")
@@ -680,7 +691,7 @@ class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
680
691
  for value in pattern:
681
692
  # Reinstantiate the model form each time to avoid overwriting the same instance. Use a mutable
682
693
  # copy of the POST QueryDict so that we can update the target field value.
683
- model_form = self.model_form(request.POST.copy())
694
+ model_form = self.model_form(request.POST.copy()) # pylint: disable=not-callable
684
695
  model_form.data[self.pattern_target] = value
685
696
 
686
697
  # Validate each new object independently.
@@ -742,8 +753,8 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
742
753
  template_name: The name of the template
743
754
  """
744
755
 
745
- queryset = None
746
- model_form = None
756
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
757
+ model_form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
747
758
  related_object_forms = {}
748
759
  template_name = "generic/object_import.html"
749
760
 
@@ -767,12 +778,15 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
767
778
  logger = logging.getLogger(__name__ + ".ObjectImportView")
768
779
  form = ImportForm(request.POST)
769
780
 
781
+ if self.model_form is None or self.queryset is None:
782
+ raise RuntimeError("self.model_form and self.queryset must not be None")
783
+
770
784
  if form.is_valid():
771
785
  logger.debug("Import form validation was successful")
772
786
 
773
787
  # Initialize model form
774
788
  data = form.cleaned_data["data"]
775
- model_form = self.model_form(data)
789
+ model_form = self.model_form(data) # pylint: disable=not-callable
776
790
  restrict_form_fields(model_form, request.user)
777
791
 
778
792
  # Assign default values for any fields which were not specified. We have to do this manually because passing
@@ -887,8 +901,8 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
887
901
  template_name: The name of the template
888
902
  """
889
903
 
890
- queryset = None
891
- table = None
904
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
905
+ table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
892
906
  template_name = "generic/object_bulk_import.html"
893
907
 
894
908
  def __init__(self, *args, **kwargs):
@@ -932,6 +946,9 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
932
946
  if form.is_valid():
933
947
  logger.debug("Form validation was successful")
934
948
 
949
+ if self.queryset is None or self.table is None:
950
+ raise RuntimeError("self.queryset and self.table must not be None")
951
+
935
952
  try:
936
953
  # Iterate through CSV data and bind each row to a new model form instance.
937
954
  with transaction.atomic():
@@ -942,7 +959,7 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
942
959
  raise ObjectDoesNotExist
943
960
 
944
961
  # Compile a table containing the imported objects
945
- obj_table = self.table(new_objs)
962
+ obj_table = self.table(new_objs) # pylint: disable=not-callable
946
963
 
947
964
  if new_objs:
948
965
  msg = f"Imported {len(new_objs)} {new_objs[0]._meta.verbose_name_plural}"
@@ -982,7 +999,7 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
982
999
  )
983
1000
 
984
1001
 
985
- class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDeleteAllModelMixin, View):
1002
+ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, BulkEditAndBulkDeleteModelMixin, View):
986
1003
  """
987
1004
  Edit objects in bulk.
988
1005
 
@@ -993,10 +1010,10 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
993
1010
  template_name: The name of the template
994
1011
  """
995
1012
 
996
- queryset = None
997
- filterset = None
998
- table = None
999
- form = None
1013
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
1014
+ filterset: Optional[type[FilterSet]] = None
1015
+ table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
1016
+ form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1000
1017
  template_name = "generic/object_bulk_edit.html"
1001
1018
 
1002
1019
  def get_required_permission(self):
@@ -1015,6 +1032,8 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
1015
1032
 
1016
1033
  def post(self, request, **kwargs):
1017
1034
  logger = logging.getLogger(__name__ + ".BulkEditView")
1035
+ if self.queryset is None or self.form is None or self.table is None:
1036
+ raise RuntimeError("self.queryset, self.form, and self.table must not be None")
1018
1037
  model = self.queryset.model
1019
1038
  edit_all = request.POST.get("_all")
1020
1039
 
@@ -1027,98 +1046,12 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
1027
1046
  queryset = self.queryset.filter(pk__in=pk_list)
1028
1047
 
1029
1048
  if "_apply" in request.POST:
1030
- form = self.form(model, request.POST, edit_all=edit_all)
1049
+ form = self.form(model, request.POST, edit_all=edit_all) # pylint: disable=not-callable
1031
1050
  restrict_form_fields(form, request.user)
1032
1051
 
1033
1052
  if form.is_valid():
1034
1053
  logger.debug("Form validation was successful")
1035
- form_custom_fields = getattr(form, "custom_fields", [])
1036
- form_relationships = getattr(form, "relationships", [])
1037
- standard_fields = [
1038
- field
1039
- for field in form.fields
1040
- if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
1041
- ]
1042
- nullified_fields = request.POST.getlist("_nullify")
1043
-
1044
- try:
1045
- with deferred_change_logging_for_bulk_operation():
1046
- updated_objects = []
1047
- queryset = queryset if edit_all else queryset.filter(pk__in=form.cleaned_data["pk"])
1048
- for obj in queryset:
1049
- obj = self.alter_obj(obj, request, [], kwargs)
1050
-
1051
- # Update standard fields. If a field is listed in _nullify, delete its value.
1052
- for name in standard_fields:
1053
- try:
1054
- model_field = model._meta.get_field(name)
1055
- except FieldDoesNotExist:
1056
- # This form field is used to modify a field rather than set its value directly
1057
- model_field = None
1058
-
1059
- # Handle nullification
1060
- if name in form.nullable_fields and name in nullified_fields:
1061
- if isinstance(model_field, ManyToManyField):
1062
- getattr(obj, name).set([])
1063
- else:
1064
- setattr(obj, name, None if model_field is not None and model_field.null else "")
1065
-
1066
- # ManyToManyFields
1067
- elif isinstance(model_field, ManyToManyField):
1068
- if form.cleaned_data[name]:
1069
- getattr(obj, name).set(form.cleaned_data[name])
1070
- # Normal fields
1071
- elif form.cleaned_data[name] not in (None, ""):
1072
- setattr(obj, name, form.cleaned_data[name])
1073
-
1074
- # Update custom fields
1075
- for field_name in form_custom_fields:
1076
- if field_name in form.nullable_fields and field_name in nullified_fields:
1077
- obj.cf[remove_prefix_from_cf_key(field_name)] = None
1078
- elif form.cleaned_data.get(field_name) not in (None, "", []):
1079
- obj.cf[remove_prefix_from_cf_key(field_name)] = form.cleaned_data[field_name]
1080
-
1081
- obj.full_clean()
1082
- obj.save()
1083
- updated_objects.append(obj)
1084
- logger.debug(f"Saved {obj} (PK: {obj.pk})")
1085
-
1086
- # Add/remove tags
1087
- if form.cleaned_data.get("add_tags", None):
1088
- obj.tags.add(*form.cleaned_data["add_tags"])
1089
- if form.cleaned_data.get("remove_tags", None):
1090
- obj.tags.remove(*form.cleaned_data["remove_tags"])
1091
-
1092
- if hasattr(form, "save_relationships") and callable(form.save_relationships):
1093
- # Add/remove relationship associations
1094
- form.save_relationships(instance=obj, nullified_fields=nullified_fields)
1095
-
1096
- if hasattr(form, "save_note") and callable(form.save_note):
1097
- form.save_note(instance=obj, user=request.user)
1098
-
1099
- self.extra_post_save_action(obj, form)
1100
-
1101
- # Enforce object-level permissions
1102
- if self.queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(
1103
- updated_objects
1104
- ):
1105
- raise ObjectDoesNotExist
1106
-
1107
- if updated_objects:
1108
- msg = f"Updated {len(updated_objects)} {model._meta.verbose_name_plural}"
1109
- logger.info(msg)
1110
- messages.success(self.request, msg)
1111
-
1112
- return redirect(self.get_return_url(request))
1113
-
1114
- except ValidationError as e:
1115
- messages.error(self.request, f"{obj} failed validation: {e}")
1116
-
1117
- except ObjectDoesNotExist:
1118
- msg = "Object update failed due to object-level permissions violation"
1119
- logger.debug(msg)
1120
- form.add_error(None, msg)
1121
-
1054
+ return self.send_bulk_edit_objects_to_job(request, form.cleaned_data, model)
1122
1055
  else:
1123
1056
  logger.debug("Form validation failed")
1124
1057
 
@@ -1134,13 +1067,13 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
1134
1067
  elif "device_type" in request.GET:
1135
1068
  initial_data["device_type"] = request.GET.get("device_type")
1136
1069
 
1137
- form = self.form(model, initial=initial_data, edit_all=edit_all)
1070
+ form = self.form(model, initial=initial_data, edit_all=edit_all) # pylint: disable=not-callable
1138
1071
  restrict_form_fields(form, request.user)
1139
1072
 
1140
1073
  # Retrieve objects being edited
1141
1074
  table = None
1142
1075
  if not edit_all:
1143
- table = self.table(queryset, orderable=False)
1076
+ table = self.table(queryset, orderable=False) # pylint: disable=not-callable
1144
1077
  if not table.rows:
1145
1078
  messages.warning(request, f"No {model._meta.verbose_name_plural} were selected.")
1146
1079
  return redirect(self.get_return_url(request))
@@ -1167,7 +1100,7 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
1167
1100
  An extendable view for renaming objects in bulk.
1168
1101
  """
1169
1102
 
1170
- queryset = None
1103
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
1171
1104
  template_name = "generic/object_bulk_rename.html"
1172
1105
 
1173
1106
  def __init__(self, *args, **kwargs):
@@ -1262,7 +1195,7 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
1262
1195
  return ""
1263
1196
 
1264
1197
 
1265
- class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDeleteAllModelMixin, View):
1198
+ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, BulkEditAndBulkDeleteModelMixin, View):
1266
1199
  """
1267
1200
  Delete objects in bulk.
1268
1201
 
@@ -1273,10 +1206,10 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
1273
1206
  template_name: The name of the template
1274
1207
  """
1275
1208
 
1276
- queryset = None
1277
- filterset = None
1278
- table = None
1279
- form = None
1209
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
1210
+ filterset: Optional[type[FilterSet]] = None
1211
+ table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
1212
+ form: Optional[type[Form]] = None
1280
1213
  template_name = "generic/object_bulk_delete.html"
1281
1214
 
1282
1215
  def get_required_permission(self):
@@ -1285,48 +1218,32 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
1285
1218
  def get(self, request):
1286
1219
  return redirect(self.get_return_url(request))
1287
1220
 
1288
- def _perform_delete_operation(self, request, queryset, model):
1289
- logger = logging.getLogger(__name__ + ".BulkDeleteView")
1290
- self.perform_pre_delete(request, queryset)
1291
- try:
1292
- _, deleted_info = bulk_delete_with_bulk_change_logging(queryset)
1293
- deleted_count = deleted_info[model._meta.label]
1294
- except ProtectedError as e:
1295
- logger.info("Caught ProtectedError while attempting to delete objects")
1296
- handle_protectederror(queryset, request, e)
1297
- return redirect(self.get_return_url(request))
1298
- msg = f"Deleted {deleted_count} {model._meta.verbose_name_plural}"
1299
- logger.info(msg)
1300
- messages.success(request, msg)
1301
- return redirect(self.get_return_url(request))
1302
-
1303
1221
  def post(self, request, **kwargs):
1304
1222
  logger = logging.getLogger(f"{__name__}.BulkDeleteView")
1223
+ if self.queryset is None or self.table is None:
1224
+ raise RuntimeError("self.queryset and self.table must not be None")
1305
1225
  model = self.queryset.model
1226
+ delete_all = request.POST.get("_all")
1306
1227
 
1307
1228
  # Are we deleting *all* objects in the queryset or just a selected subset?
1308
- if request.POST.get("_all"):
1229
+ if delete_all:
1309
1230
  queryset = self._get_bulk_edit_delete_all_queryset(request)
1310
-
1311
- if "_confirm" in request.POST:
1312
- return self._perform_delete_operation(request, queryset, model)
1313
-
1314
- context = self._bulk_delete_all_context(request, queryset)
1315
- context.update(self.extra_context())
1316
- return render(request, self.template_name, context)
1317
-
1318
- pk_list = request.POST.getlist("pk")
1231
+ pk_list = []
1232
+ else:
1233
+ pk_list = request.POST.getlist("pk")
1234
+ queryset = self.queryset.filter(pk__in=pk_list)
1319
1235
 
1320
1236
  form_cls = self.get_form()
1321
1237
 
1322
1238
  if "_confirm" in request.POST:
1323
1239
  form = form_cls(request.POST)
1240
+ # Set pk field requirement here instead of BulkDeleteForm since get_form() may return a different form class
1241
+ if delete_all:
1242
+ form.fields["pk"].required = False
1243
+
1324
1244
  if form.is_valid():
1325
1245
  logger.debug("Form validation was successful")
1326
-
1327
- # Delete objects
1328
- queryset = self.queryset.filter(pk__in=pk_list)
1329
- return self._perform_delete_operation(request, queryset, model)
1246
+ return self.send_bulk_delete_objects_to_job(request, pk_list, model, delete_all)
1330
1247
  else:
1331
1248
  logger.debug("Form validation failed")
1332
1249
 
@@ -1339,23 +1256,26 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
1339
1256
  )
1340
1257
 
1341
1258
  # Retrieve objects being deleted
1342
- table = self.table(self.queryset.filter(pk__in=pk_list), orderable=False)
1343
- if not table.rows:
1344
- messages.warning(
1345
- request,
1346
- f"No {model._meta.verbose_name_plural} were selected for deletion.",
1347
- )
1348
- return redirect(self.get_return_url(request))
1349
- # Hide actions column if present
1350
- if "actions" in table.columns:
1351
- table.columns.hide("actions")
1259
+ table = None
1260
+ if not delete_all:
1261
+ table = self.table(queryset, orderable=False) # pylint: disable=not-callable
1262
+ if not table.rows:
1263
+ messages.warning(
1264
+ request,
1265
+ f"No {model._meta.verbose_name_plural} were selected for deletion.",
1266
+ )
1267
+ return redirect(self.get_return_url(request))
1268
+ # Hide actions column if present
1269
+ if "actions" in table.columns:
1270
+ table.columns.hide("actions")
1352
1271
 
1353
1272
  context = {
1354
1273
  "form": form,
1355
1274
  "obj_type_plural": model._meta.verbose_name_plural,
1356
1275
  "table": table,
1357
1276
  "return_url": self.get_return_url(request),
1358
- "total_objs_to_delete": len(table.rows),
1277
+ "total_objs_to_delete": queryset.count(),
1278
+ "delete_all": delete_all,
1359
1279
  }
1360
1280
  context.update(self.extra_context())
1361
1281
  return render(request, self.template_name, context)
@@ -1391,17 +1311,19 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1391
1311
  Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
1392
1312
  """
1393
1313
 
1394
- queryset = None
1395
- form = None
1396
- model_form = None
1314
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
1315
+ form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1316
+ model_form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1397
1317
  template_name = "dcim/device_component_add.html"
1398
1318
 
1399
1319
  def get_required_permission(self):
1400
1320
  return get_permission_for_model(self.queryset.model, "add")
1401
1321
 
1402
1322
  def get(self, request):
1403
- form = self.form(initial=normalize_querydict(request.GET, form_class=self.form))
1404
- model_form = self.model_form(request.GET)
1323
+ if self.form is None or self.model_form is None:
1324
+ raise RuntimeError("self.form and self.model_form must not be None")
1325
+ form = self.form(initial=normalize_querydict(request.GET, form_class=self.form)) # pylint: disable=not-callable
1326
+ model_form = self.model_form(request.GET) # pylint: disable=not-callable
1405
1327
 
1406
1328
  return render(
1407
1329
  request,
@@ -1416,8 +1338,10 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1416
1338
 
1417
1339
  def post(self, request):
1418
1340
  logger = logging.getLogger(__name__ + ".ComponentCreateView")
1419
- form = self.form(request.POST, initial=normalize_querydict(request.GET, form_class=self.form))
1420
- model_form = self.model_form(request.POST, initial=normalize_querydict(request.GET, form_class=self.model_form))
1341
+ if self.form is None or self.model_form is None or self.queryset is None:
1342
+ raise RuntimeError("self.form, self.model_form, and self.queryset must not be None")
1343
+ form = self.form(request.POST, initial=normalize_querydict(request.GET, form_class=self.form)) # pylint: disable=not-callable
1344
+ model_form = self.model_form(request.POST, initial=normalize_querydict(request.GET, form_class=self.model_form)) # pylint: disable=not-callable
1421
1345
 
1422
1346
  if form.is_valid():
1423
1347
  new_components = []
@@ -1432,7 +1356,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1432
1356
  data["label"] = label
1433
1357
  if hasattr(form, "get_iterative_data"):
1434
1358
  data.update(form.get_iterative_data(i))
1435
- component_form = self.model_form(
1359
+ component_form = self.model_form( # pylint: disable=not-callable
1436
1360
  data, initial=normalize_querydict(request.GET, form_class=self.model_form)
1437
1361
  )
1438
1362
 
@@ -1493,13 +1417,13 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
1493
1417
  Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
1494
1418
  """
1495
1419
 
1496
- parent_model = None
1420
+ parent_model: Optional[type[Model]] = None # TODO: required, declared Optional only to avoid a breaking change
1497
1421
  parent_field = None
1498
- form = None
1499
- queryset = None
1500
- model_form = None
1501
- filterset = None
1502
- table = None
1422
+ form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1423
+ queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
1424
+ model_form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
1425
+ filterset: Optional[type[FilterSet]] = None
1426
+ table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
1503
1427
  template_name = "generic/object_bulk_add_component.html"
1504
1428
 
1505
1429
  def get_required_permission(self):
@@ -1507,13 +1431,23 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
1507
1431
 
1508
1432
  def post(self, request):
1509
1433
  logger = logging.getLogger(__name__ + ".BulkComponentCreateView")
1434
+ if (
1435
+ self.form is None
1436
+ or self.model_form is None
1437
+ or self.parent_model is None
1438
+ or self.queryset is None
1439
+ or self.table is None
1440
+ ):
1441
+ raise RuntimeError(
1442
+ "self.form, self.model_form, self.parent_model, self.queryset, and self.table must not be None"
1443
+ )
1510
1444
  parent_model_name = self.parent_model._meta.verbose_name_plural
1511
1445
  model_name = self.queryset.model._meta.verbose_name_plural
1512
1446
  model = self.queryset.model
1513
1447
 
1514
1448
  # Are we editing *all* objects in the queryset or just a selected subset?
1515
1449
  if request.POST.get("_all") and self.filterset is not None:
1516
- pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only("pk")).qs]
1450
+ pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only("pk")).qs] # pylint: disable=not-callable
1517
1451
  else:
1518
1452
  pk_list = request.POST.getlist("pk")
1519
1453
 
@@ -1524,10 +1458,10 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
1524
1458
  f"No {self.parent_model._meta.verbose_name_plural} were selected.",
1525
1459
  )
1526
1460
  return redirect(self.get_return_url(request))
1527
- table = self.table(selected_objects)
1461
+ table = self.table(selected_objects) # pylint: disable=not-callable
1528
1462
 
1529
1463
  if "_create" in request.POST:
1530
- form = self.form(model, request.POST)
1464
+ form = self.form(model, request.POST) # pylint: disable=not-callable
1531
1465
 
1532
1466
  if form.is_valid():
1533
1467
  logger.debug("Form validation was successful")
@@ -1549,7 +1483,7 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
1549
1483
  "label": label,
1550
1484
  }
1551
1485
  component_data.update(data)
1552
- component_form = self.model_form(component_data)
1486
+ component_form = self.model_form(component_data) # pylint: disable=not-callable
1553
1487
  if component_form.is_valid():
1554
1488
  instance = component_form.save()
1555
1489
  logger.debug(f"Created {instance} on {instance.parent}")
@@ -1591,7 +1525,7 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
1591
1525
  logger.debug("Form validation failed")
1592
1526
 
1593
1527
  else:
1594
- form = self.form(model, initial={"pk": pk_list})
1528
+ form = self.form(model, initial={"pk": pk_list}) # pylint: disable=not-callable
1595
1529
 
1596
1530
  return render(
1597
1531
  request,