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
nautobot/core/filters.py CHANGED
@@ -13,6 +13,7 @@ from django_filters.constants import EMPTY_VALUES
13
13
  from django_filters.utils import get_model_field, label_for_filter, resolve_field, verbose_lookup_expr
14
14
  from drf_spectacular.types import OpenApiTypes
15
15
  from drf_spectacular.utils import extend_schema_field
16
+ import timezone_field
16
17
 
17
18
  from nautobot.core import constants, forms
18
19
  from nautobot.core.forms import widgets
@@ -612,12 +613,16 @@ class BaseFilterSet(django_filters.FilterSet):
612
613
  models.UUIDField: {"filter_class": MultiValueUUIDFilter},
613
614
  core_fields.MACAddressCharField: {"filter_class": MultiValueMACAddressFilter},
614
615
  core_fields.TagsField: {"filter_class": TagFilter},
616
+ timezone_field.TimeZoneField: {"filter_class": MultiValueCharFilter},
615
617
  }
616
618
  )
617
619
 
620
+ USE_CHAR_FILTER_FOR_LOOKUPS = [django_filters.MultipleChoiceFilter]
621
+
618
622
  @staticmethod
619
623
  def _get_filter_lookup_dict(existing_filter):
620
624
  # Choose the lookup expression map based on the filter type
625
+
621
626
  if isinstance(
622
627
  existing_filter,
623
628
  (
@@ -637,6 +642,7 @@ class BaseFilterSet(django_filters.FilterSet):
637
642
  (
638
643
  django_filters.ModelChoiceFilter,
639
644
  django_filters.ModelMultipleChoiceFilter,
645
+ MultiValueUUIDFilter,
640
646
  TagFilter,
641
647
  TreeNodeMultipleChoiceFilter,
642
648
  ),
@@ -666,7 +672,7 @@ class BaseFilterSet(django_filters.FilterSet):
666
672
  the form `<field_name>__<lookup_expr>`
667
673
  """
668
674
  magic_filters = {}
669
- if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in"]:
675
+ if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in", "iexact"]:
670
676
  return magic_filters
671
677
 
672
678
  # Choose the lookup expression map based on the filter type
@@ -676,8 +682,7 @@ class BaseFilterSet(django_filters.FilterSet):
676
682
  return magic_filters
677
683
 
678
684
  # Get properties of the existing filter for later use
679
- field_name = filter_field.field_name
680
- field = get_model_field(cls._meta.model, field_name)
685
+ field = get_model_field(cls._meta.model, filter_field.field_name) # pylint: disable=no-member
681
686
 
682
687
  # If there isn't a model field, return.
683
688
  if field is None:
@@ -694,25 +699,7 @@ class BaseFilterSet(django_filters.FilterSet):
694
699
  new_filter_name = f"{filter_name}__{lookup_name}"
695
700
 
696
701
  try:
697
- if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}:
698
- # The filter field has been explicitly defined on the filterset class so we must manually
699
- # create the new filter with the same type because there is no guarantee the defined type
700
- # is the same as the default type for the field. This does not apply if the filter
701
- # should retain the original lookup_expr type, such as `isnull` using a boolean field on a
702
- # char or date object.
703
- resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
704
- new_filter = type(filter_field)(
705
- field_name=field_name,
706
- lookup_expr=lookup_expr,
707
- label=filter_field.label,
708
- exclude=filter_field.exclude,
709
- distinct=filter_field.distinct,
710
- **filter_field.extra,
711
- )
712
- else:
713
- # The filter field is listed in Meta.fields so we can safely rely on default behavior
714
- # Will raise FieldLookupError if the lookup is invalid
715
- new_filter = cls.filter_for_field(field, field_name, lookup_expr)
702
+ new_filter = cls._get_new_filter(filter_field, field, filter_name, lookup_expr)
716
703
  except django_filters.exceptions.FieldLookupError:
717
704
  # The filter could not be created because the lookup expression is not supported on the field
718
705
  continue
@@ -725,7 +712,10 @@ class BaseFilterSet(django_filters.FilterSet):
725
712
  # If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
726
713
  # so we have to do it.
727
714
  if filter_field.label and filter_field.label != label_for_filter(
728
- cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
715
+ cls._meta.model, # pylint: disable=no-member
716
+ filter_field.field_name,
717
+ filter_field.lookup_expr,
718
+ filter_field.exclude,
729
719
  ):
730
720
  # Lightly adjusted from label_for_filter() implementation:
731
721
  verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
@@ -738,6 +728,45 @@ class BaseFilterSet(django_filters.FilterSet):
738
728
 
739
729
  return magic_filters
740
730
 
731
+ @classmethod
732
+ def _should_use_char_filter_for_lookups(cls, filter_field):
733
+ return type(filter_field) in cls.USE_CHAR_FILTER_FOR_LOOKUPS
734
+
735
+ @classmethod
736
+ def _get_new_filter(cls, filter_field, field, filter_name, lookup_expr):
737
+ if cls._should_use_char_filter_for_lookups(filter_field):
738
+ # For some cases like `MultiValueChoiceFilter(django_filters.MultipleChoiceFilter)`
739
+ # we want to have choices field with no lookups and standard char field for lookups filtering.
740
+ # Using a `choice` field for lookups blocks us from using `__re`, `__iew` or other "partial" filters.
741
+ resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
742
+ return MultiValueCharFilter(
743
+ field_name=filter_field.field_name,
744
+ lookup_expr=lookup_expr,
745
+ label=filter_field.label,
746
+ exclude=filter_field.exclude,
747
+ distinct=filter_field.distinct,
748
+ )
749
+
750
+ if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}: # pylint: disable=no-member
751
+ # The filter field has been explicitly defined on the filterset class so we must manually
752
+ # create the new filter with the same type because there is no guarantee the defined type
753
+ # is the same as the default type for the field. This does not apply if the filter
754
+ # should retain the original lookup_expr type, such as `isnull` using a boolean field on a
755
+ # char or date object.
756
+ resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
757
+ return type(filter_field)(
758
+ field_name=filter_field.field_name,
759
+ lookup_expr=lookup_expr,
760
+ label=filter_field.label,
761
+ exclude=filter_field.exclude,
762
+ distinct=filter_field.distinct,
763
+ **filter_field.extra,
764
+ )
765
+
766
+ # The filter field is listed in Meta.fields so we can safely rely on default behavior
767
+ # Will raise FieldLookupError if the lookup is invalid
768
+ return cls.filter_for_field(field, filter_field.field_name, lookup_expr)
769
+
741
770
  @classmethod
742
771
  def add_filter(cls, new_filter_name, new_filter_field):
743
772
  """
@@ -748,22 +777,22 @@ class BaseFilterSet(django_filters.FilterSet):
748
777
  if not isinstance(new_filter_field, django_filters.Filter):
749
778
  raise TypeError(f"Tried to add filter ({new_filter_name}) which is not an instance of Django Filter")
750
779
 
751
- if new_filter_name in cls.base_filters:
780
+ if new_filter_name in cls.base_filters: # pylint: disable=no-member
752
781
  raise AttributeError(
753
782
  f"There was a conflict with filter `{new_filter_name}`, the custom filter was ignored."
754
783
  )
755
784
 
756
- cls.base_filters[new_filter_name] = new_filter_field
785
+ cls.base_filters[new_filter_name] = new_filter_field # pylint: disable=no-member
757
786
  # django-filters has no concept of "abstract" filtersets, so we have to fake it
758
- if cls._meta.model is not None:
759
- cls.base_filters.update(
787
+ if cls._meta.model is not None: # pylint: disable=no-member
788
+ cls.base_filters.update( # pylint: disable=no-member
760
789
  cls._generate_lookup_expression_filters(filter_name=new_filter_name, filter_field=new_filter_field)
761
790
  )
762
791
 
763
792
  @classmethod
764
793
  def get_fields(cls):
765
794
  fields = super().get_fields()
766
- if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude):
795
+ if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude): # pylint: disable=no-member
767
796
  # Add "id" as the first key in the `fields` dict
768
797
  fields = {"id": [django_filters.conf.settings.DEFAULT_LOOKUP_EXPR], **fields}
769
798
  return fields
@@ -780,7 +809,7 @@ class BaseFilterSet(django_filters.FilterSet):
780
809
  if filter_name.startswith("_"):
781
810
  del filters[filter_name]
782
811
 
783
- if getattr(cls._meta.model, "is_contact_associable_model", False):
812
+ if getattr(cls._meta.model, "is_contact_associable_model", False): # pylint: disable=no-member
784
813
  # Add "contacts" and "teams" filters
785
814
  from nautobot.extras.models import Contact, Team
786
815
 
@@ -800,13 +829,13 @@ class BaseFilterSet(django_filters.FilterSet):
800
829
  label="Teams (name or ID)",
801
830
  )
802
831
 
803
- if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False):
804
- if not hasattr(cls._meta.model, "static_group_association_set"):
832
+ if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False): # pylint: disable=no-member
833
+ if not hasattr(cls._meta.model, "static_group_association_set"): # pylint: disable=no-member
805
834
  logger.warning(
806
835
  "Model %s has 'is_dynamic_group_associable_model = True' but lacks "
807
836
  "a 'static_group_association_set' attribute. Perhaps this is due to it inheriting from "
808
837
  "the deprecated DynamicGroupMixin class instead of the preferred DynamicGroupsModelMixin?",
809
- cls._meta.model,
838
+ cls._meta.model, # pylint: disable=no-member
810
839
  )
811
840
  else:
812
841
  # Add "dynamic_groups" field as the last key
@@ -816,14 +845,14 @@ class BaseFilterSet(django_filters.FilterSet):
816
845
  queryset=DynamicGroup.objects.all(),
817
846
  field_name="static_group_association_set__dynamic_group",
818
847
  to_field_name="name",
819
- query_params={"content_type": cls._meta.model._meta.label_lower},
848
+ query_params={"content_type": cls._meta.model._meta.label_lower}, # pylint: disable=no-member
820
849
  label="Dynamic groups (name or ID)",
821
850
  )
822
851
 
823
852
  # django-filters has no concept of "abstract" filtersets, so we have to fake it
824
- if cls._meta.model is not None:
853
+ if cls._meta.model is not None: # pylint: disable=no-member
825
854
  if "tags" in filters and isinstance(filters["tags"], TagFilter):
826
- filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]}
855
+ filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]} # pylint: disable=no-member
827
856
 
828
857
  new_filters = {}
829
858
  for existing_filter_name, existing_filter in filters.items():
@@ -845,7 +874,10 @@ class BaseFilterSet(django_filters.FilterSet):
845
874
  Note: Any CharField or IntegerField with choices set is a ChoiceField.
846
875
  """
847
876
  if lookup_type == "exact" and getattr(field, "choices", None):
877
+ if isinstance(field, timezone_field.TimeZoneField):
878
+ return django_filters.MultipleChoiceFilter, {"choices": ((str(v), n) for v, n in field.choices)}
848
879
  return django_filters.MultipleChoiceFilter, {"choices": field.choices}
880
+
849
881
  return super().filter_for_lookup(field, lookup_type)
850
882
 
851
883
  def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
@@ -76,24 +76,21 @@ from nautobot.core.forms.widgets import (
76
76
  )
77
77
 
78
78
  __all__ = (
79
- "add_blank_choice",
80
- "add_field_to_filter_form_class",
81
- "AddressFieldMixin",
82
79
  "ALPHANUMERIC_EXPANSION_PATTERN",
80
+ "BOOLEAN_CHOICES",
81
+ "BOOLEAN_WITH_BLANK_CHOICES",
82
+ "IP4_EXPANSION_PATTERN",
83
+ "IP6_EXPANSION_PATTERN",
84
+ "NUMERIC_EXPANSION_PATTERN",
83
85
  "APISelect",
84
86
  "APISelectMultiple",
87
+ "AddressFieldMixin",
85
88
  "AutoPositionField",
86
89
  "AutoPositionPatternField",
87
- "BOOLEAN_CHOICES",
88
- "BOOLEAN_WITH_BLANK_CHOICES",
89
90
  "BootstrapMixin",
90
91
  "BulkEditForm",
91
92
  "BulkEditNullBooleanSelect",
92
93
  "BulkRenameForm",
93
- "ColorSelect",
94
- "CommentField",
95
- "ConfirmationForm",
96
- "ContentTypeSelect",
97
94
  "CSVChoiceField",
98
95
  "CSVContentTypeField",
99
96
  "CSVDataField",
@@ -102,34 +99,29 @@ __all__ = (
102
99
  "CSVModelForm",
103
100
  "CSVMultipleChoiceField",
104
101
  "CSVMultipleContentTypeField",
102
+ "ColorSelect",
103
+ "CommentField",
104
+ "ConfirmationForm",
105
+ "ContentTypeSelect",
105
106
  "DatePicker",
106
107
  "DateTimePicker",
107
108
  "DynamicFilterForm",
108
109
  "DynamicModelChoiceField",
109
110
  "DynamicModelMultipleChoiceField",
110
- "expand_alphanumeric_pattern",
111
- "expand_ipaddress_pattern",
112
111
  "ExpandableIPAddressField",
113
112
  "ExpandableNameField",
114
- "form_from_model",
115
113
  "ImportForm",
116
- "IP4_EXPANSION_PATTERN",
117
- "IP6_EXPANSION_PATTERN",
118
114
  "JSONArrayFormField",
119
115
  "JSONField",
120
116
  "LaxURLField",
121
117
  "MACAddressField",
122
118
  "MultiMatchModelMultipleChoiceField",
123
- "MultipleContentTypeField",
124
119
  "MultiValueCharField",
125
120
  "MultiValueCharInput",
121
+ "MultipleContentTypeField",
126
122
  "NullableDateField",
127
- "NUMERIC_EXPANSION_PATTERN",
128
123
  "NumericArrayField",
129
- "parse_alphanumeric_range",
130
- "parse_numeric_range",
131
124
  "PrefixFieldMixin",
132
- "restrict_form_fields",
133
125
  "ReturnURLForm",
134
126
  "SearchForm",
135
127
  "SelectWithDisabled",
@@ -142,4 +134,12 @@ __all__ = (
142
134
  "TableConfigForm",
143
135
  "TagFilterField",
144
136
  "TimePicker",
137
+ "add_blank_choice",
138
+ "add_field_to_filter_form_class",
139
+ "expand_alphanumeric_pattern",
140
+ "expand_ipaddress_pattern",
141
+ "form_from_model",
142
+ "parse_alphanumeric_range",
143
+ "parse_numeric_range",
144
+ "restrict_form_fields",
145
145
  )
@@ -22,7 +22,6 @@ from nautobot.core.models import validators
22
22
  from nautobot.core.utils import data as data_utils, lookup
23
23
 
24
24
  __all__ = (
25
- "CommentField",
26
25
  "CSVChoiceField",
27
26
  "CSVContentTypeField",
28
27
  "CSVDataField",
@@ -30,16 +29,17 @@ __all__ = (
30
29
  "CSVModelChoiceField",
31
30
  "CSVMultipleChoiceField",
32
31
  "CSVMultipleContentTypeField",
32
+ "CommentField",
33
33
  "DynamicModelChoiceField",
34
34
  "DynamicModelMultipleChoiceField",
35
35
  "ExpandableIPAddressField",
36
36
  "ExpandableNameField",
37
- "JSONField",
38
37
  "JSONArrayFormField",
38
+ "JSONField",
39
39
  "LaxURLField",
40
40
  "MACAddressField",
41
- "MultipleContentTypeField",
42
41
  "MultiMatchModelMultipleChoiceField",
42
+ "MultipleContentTypeField",
43
43
  "NumericArrayField",
44
44
  "SlugField",
45
45
  "TagFilterField",
@@ -102,13 +102,13 @@ class CSVFileField(django_forms.FileField):
102
102
  "in double quotes."
103
103
  )
104
104
 
105
- def to_python(self, file):
105
+ def to_python(self, data):
106
106
  """For parity with CSVDataField, this returns the CSV text rather than an UploadedFile object."""
107
- if file is None:
107
+ if data is None:
108
108
  return None
109
109
 
110
- file = super().to_python(file)
111
- return file.read().decode("utf-8-sig").strip()
110
+ data = super().to_python(data)
111
+ return data.read().decode("utf-8-sig").strip()
112
112
 
113
113
 
114
114
  class CSVChoiceField(django_forms.ChoiceField):
@@ -504,6 +504,8 @@ class DynamicModelChoiceMixin:
504
504
  ):
505
505
  self.display_field = display_field
506
506
  self.query_params = query_params or {}
507
+ # Default to "exclude_m2m=true" for improved performance, if not otherwise specified
508
+ self.query_params.setdefault("exclude_m2m", "true")
507
509
  self.initial_params = initial_params or {}
508
510
  self.null_option = null_option
509
511
  self.disabled_indicator = disabled_indicator
@@ -536,9 +538,10 @@ class DynamicModelChoiceMixin:
536
538
  # Toggle depth
537
539
  attrs["data-depth"] = self.depth
538
540
 
539
- # Attach any static query parameters
540
- for key, value in self.query_params.items():
541
- widget.add_query_param(key, value)
541
+ # Attach any static query parameters if supported
542
+ if isinstance(widget, widgets.APISelect) or hasattr(widget, "add_query_param"):
543
+ for key, value in self.query_params.items():
544
+ widget.add_query_param(key, value)
542
545
 
543
546
  return attrs
544
547
 
@@ -819,7 +822,7 @@ class MultiMatchModelMultipleChoiceField(DynamicModelChoiceMixin, django_filters
819
822
  self.natural_key = kwargs.setdefault("to_field_name", "slug")
820
823
  super().__init__(*args, **kwargs)
821
824
 
822
- def _check_values(self, values):
825
+ def _check_values(self, values): # pylint:disable=arguments-renamed
823
826
  """
824
827
  This method overloads the grandparent method in `django.forms.models.ModelMultipleChoiceField`,
825
828
  re-using some of that method's existing logic and adding support for coupling this field with
@@ -1,8 +1,11 @@
1
+ import contextlib
1
2
  import json
2
3
  import logging
3
4
  import re
4
5
 
5
6
  from django import forms
7
+ from django.core.exceptions import FieldDoesNotExist
8
+ from django.db.models.fields.related import ManyToManyField
6
9
  from django.forms import formset_factory
7
10
  from django.urls import reverse
8
11
  import yaml
@@ -15,8 +18,8 @@ __all__ = (
15
18
  "BootstrapMixin",
16
19
  "BulkEditForm",
17
20
  "BulkRenameForm",
18
- "ConfirmationForm",
19
21
  "CSVModelForm",
22
+ "ConfirmationForm",
20
23
  "DynamicFilterForm",
21
24
  "ImportForm",
22
25
  "PrefixFieldMixin",
@@ -124,7 +127,35 @@ class BulkEditForm(forms.Form):
124
127
 
125
128
  if edit_all:
126
129
  self.fields["pk"].required = False
127
- self.fields["_all"] = forms.BooleanField(widget=forms.HiddenInput(), required=True, initial=True)
130
+ self.fields["_all"] = forms.BooleanField(widget=forms.HiddenInput(), required=False, initial=True)
131
+
132
+ def _save_m2m_fields(self, obj):
133
+ """Save M2M fields"""
134
+ from nautobot.core.models.fields import TagsField # Avoid circular dependency
135
+
136
+ m2m_field_names = []
137
+ # Handle M2M Save
138
+ for key in self.cleaned_data.keys():
139
+ if key.startswith(("add_", "remove_")):
140
+ field_name = key.lstrip("add_")
141
+ if field_name in m2m_field_names:
142
+ continue
143
+ with contextlib.suppress(FieldDoesNotExist):
144
+ field = obj._meta.get_field(field_name)
145
+ is_m2m_field = isinstance(field, (ManyToManyField, TagsField))
146
+ if is_m2m_field:
147
+ m2m_field_names.append(field_name)
148
+
149
+ for field_name in m2m_field_names:
150
+ m2m_field = getattr(obj, field_name)
151
+ if self.cleaned_data.get(f"add_{field_name}", None):
152
+ m2m_field.add(*self.cleaned_data[f"add_{field_name}"])
153
+ if self.cleaned_data.get(f"remove_{field_name}", None):
154
+ m2m_field.remove(*self.cleaned_data[f"remove_{field_name}"])
155
+
156
+ def post_save(self, obj):
157
+ """Post save action"""
158
+ self._save_m2m_fields(obj)
128
159
 
129
160
 
130
161
  class BulkRenameForm(forms.Form):
@@ -10,7 +10,7 @@ class OptimizedNautobotObjectType(gql_optimizer.OptimizedDjangoObjectType):
10
10
  url = graphene.String()
11
11
 
12
12
  def resolve_url(self, info):
13
- return self.get_absolute_url(api=True)
13
+ return self.get_absolute_url(api=True) # pylint: disable=no-member
14
14
 
15
15
  class Meta:
16
16
  abstract = True
@@ -4,7 +4,9 @@ from io import BytesIO
4
4
 
5
5
  from django.conf import settings
6
6
  from django.contrib.contenttypes.models import ContentType
7
- from django.core.exceptions import PermissionDenied
7
+ from django.core.exceptions import (
8
+ PermissionDenied,
9
+ )
8
10
  from django.db import transaction
9
11
  from django.http import QueryDict
10
12
  from rest_framework import exceptions as drf_exceptions
@@ -15,6 +17,7 @@ from nautobot.core.api.renderers import NautobotCSVRenderer
15
17
  from nautobot.core.api.utils import get_serializer_for_model
16
18
  from nautobot.core.celery import app, register_jobs
17
19
  from nautobot.core.exceptions import AbortTransaction
20
+ from nautobot.core.jobs.bulk_actions import BulkDeleteObjects, BulkEditObjects
18
21
  from nautobot.core.jobs.cleanup import LogsCleanup
19
22
  from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
20
23
  from nautobot.core.utils.lookup import get_filterset_for_model
@@ -25,7 +28,16 @@ from nautobot.extras.datasources import (
25
28
  refresh_datasource_content,
26
29
  refresh_job_code_from_repository,
27
30
  )
28
- from nautobot.extras.jobs import BooleanVar, ChoiceVar, FileVar, Job, ObjectVar, RunJobTaskFailed, StringVar, TextVar
31
+ from nautobot.extras.jobs import (
32
+ BooleanVar,
33
+ ChoiceVar,
34
+ FileVar,
35
+ Job,
36
+ ObjectVar,
37
+ RunJobTaskFailed,
38
+ StringVar,
39
+ TextVar,
40
+ )
29
41
  from nautobot.extras.models import ExportTemplate, GitRepository
30
42
 
31
43
  name = "System Jobs"
@@ -47,7 +59,7 @@ class GitRepositorySync(Job):
47
59
  description = "Clone and/or pull a Git repository, then refresh data sourced from this repository."
48
60
  has_sensitive_variables = False
49
61
 
50
- def run(self, repository):
62
+ def run(self, repository): # pylint:disable=arguments-differ
51
63
  job_result = self.job_result
52
64
  user = job_result.user
53
65
 
@@ -88,7 +100,7 @@ class GitRepositoryDryRun(Job):
88
100
  description = "Dry run of Git repository sync - will not update data sourced from this repository."
89
101
  has_sensitive_variables = False
90
102
 
91
- def run(self, repository):
103
+ def run(self, repository): # pylint:disable=arguments-differ
92
104
  job_result = self.job_result
93
105
  self.logger.info(f'Performing a Dry Run on Git repository "{repository.name}"...')
94
106
 
@@ -138,7 +150,7 @@ class ExportObjectList(Job):
138
150
  soft_time_limit = 1800
139
151
  time_limit = 2000
140
152
 
141
- def run(self, *, content_type, query_string="", export_format="csv", export_template=None):
153
+ def run(self, *, content_type, query_string="", export_format="csv", export_template=None): # pylint:disable=arguments-differ
142
154
  if not self.user.has_perm(f"{content_type.app_label}.view_{content_type.model}"):
143
155
  self.logger.error('User "%s" does not have permission to view %s objects', self.user, content_type.model)
144
156
  raise PermissionDenied("User does not have view permissions on the requested content-type")
@@ -286,7 +298,7 @@ class ImportObjects(Job):
286
298
  self.logger.error("Row %d: `%s`: `%s`", row, field, err[0])
287
299
  return new_objs, validation_failed
288
300
 
289
- def run(self, *, content_type, csv_data=None, csv_file=None, roll_back_if_error=False):
301
+ def run(self, *, content_type, csv_data=None, csv_file=None, roll_back_if_error=False): # pylint:disable=arguments-differ
290
302
  if not self.user.has_perm(f"{content_type.app_label}.add_{content_type.model}"):
291
303
  self.logger.error('User "%s" does not have permission to create %s objects', self.user, content_type.model)
292
304
  raise PermissionDenied("User does not have create permissions on the requested content-type")
@@ -347,5 +359,14 @@ class ImportObjects(Job):
347
359
  raise RunJobTaskFailed("CSV import not fully successful, see logs")
348
360
 
349
361
 
350
- jobs = [ExportObjectList, GitRepositorySync, GitRepositoryDryRun, ImportObjects, LogsCleanup, RefreshDynamicGroupCaches]
362
+ jobs = [
363
+ BulkDeleteObjects,
364
+ BulkEditObjects,
365
+ ExportObjectList,
366
+ GitRepositorySync,
367
+ GitRepositoryDryRun,
368
+ ImportObjects,
369
+ LogsCleanup,
370
+ RefreshDynamicGroupCaches,
371
+ ]
351
372
  register_jobs(*jobs)