nautobot 2.3.16__py3-none-any.whl → 2.4.0b1__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 (675) hide show
  1. nautobot/__init__.py +15 -0
  2. nautobot/apps/api.py +0 -2
  3. nautobot/apps/config.py +32 -3
  4. nautobot/apps/events.py +19 -0
  5. nautobot/apps/exceptions.py +0 -2
  6. nautobot/apps/ui.py +44 -9
  7. nautobot/apps/utils.py +0 -8
  8. nautobot/apps/views.py +2 -0
  9. nautobot/circuits/navigation.py +0 -57
  10. nautobot/circuits/tables.py +1 -2
  11. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -71
  12. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -64
  13. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +40 -0
  14. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +26 -0
  15. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -76
  16. nautobot/circuits/tests/integration/test_relationships.py +33 -24
  17. nautobot/circuits/tests/test_filters.py +4 -8
  18. nautobot/circuits/views.py +143 -26
  19. nautobot/cloud/factory.py +4 -1
  20. nautobot/cloud/models.py +1 -1
  21. nautobot/cloud/tests/test_filters.py +5 -4
  22. nautobot/core/api/fields.py +5 -5
  23. nautobot/core/api/metadata.py +28 -256
  24. nautobot/core/api/pagination.py +3 -2
  25. nautobot/core/api/renderers.py +3 -0
  26. nautobot/core/api/serializers.py +24 -244
  27. nautobot/core/api/urls.py +3 -4
  28. nautobot/core/api/utils.py +0 -62
  29. nautobot/core/api/views.py +48 -158
  30. nautobot/core/apps/__init__.py +22 -578
  31. nautobot/core/celery/__init__.py +13 -0
  32. nautobot/core/celery/log.py +4 -4
  33. nautobot/core/celery/schedulers.py +48 -3
  34. nautobot/core/cli/__init__.py +8 -0
  35. nautobot/core/constants.py +7 -0
  36. nautobot/core/events/__init__.py +116 -0
  37. nautobot/core/events/base.py +27 -0
  38. nautobot/core/events/exceptions.py +10 -0
  39. nautobot/core/events/redis_broker.py +48 -0
  40. nautobot/core/events/syslog_broker.py +19 -0
  41. nautobot/core/exceptions.py +0 -6
  42. nautobot/core/filters.py +16 -21
  43. nautobot/core/fixtures/user-data.json +59 -0
  44. nautobot/core/forms/fields.py +53 -8
  45. nautobot/core/forms/utils.py +2 -1
  46. nautobot/core/graphql/schema.py +3 -1
  47. nautobot/core/graphql/types.py +1 -1
  48. nautobot/core/jobs/__init__.py +4 -4
  49. nautobot/core/jobs/cleanup.py +13 -49
  50. nautobot/core/jobs/groups.py +1 -1
  51. nautobot/core/management/commands/generate_test_data.py +21 -0
  52. nautobot/core/management/commands/validate_models.py +1 -1
  53. nautobot/core/middleware.py +16 -0
  54. nautobot/core/models/__init__.py +1 -1
  55. nautobot/core/models/fields.py +11 -7
  56. nautobot/core/models/query_functions.py +2 -2
  57. nautobot/core/models/tree_queries.py +3 -6
  58. nautobot/core/settings.py +44 -7
  59. nautobot/core/settings.yaml +86 -8
  60. nautobot/core/tables.py +15 -65
  61. nautobot/core/tasks.py +1 -1
  62. nautobot/core/templates/components/button/default.html +7 -0
  63. nautobot/core/templates/components/button/dropdown.html +20 -0
  64. nautobot/core/templates/components/layout/one_over_two.html +19 -0
  65. nautobot/core/templates/components/layout/two_over_one.html +19 -0
  66. nautobot/core/templates/components/panel/body_content_data_table.html +27 -0
  67. nautobot/core/templates/components/panel/body_content_objects_table.html +4 -0
  68. nautobot/core/templates/components/panel/body_content_tags.html +6 -0
  69. nautobot/core/templates/components/panel/body_content_text.html +12 -0
  70. nautobot/core/templates/components/panel/body_wrapper_generic.html +3 -0
  71. nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +3 -0
  72. nautobot/core/templates/components/panel/body_wrapper_table.html +3 -0
  73. nautobot/core/templates/components/panel/footer_contacts_table.html +20 -0
  74. nautobot/core/templates/components/panel/footer_content_table.html +14 -0
  75. nautobot/core/templates/components/panel/grouping_toggle.html +14 -0
  76. nautobot/core/templates/components/panel/header_extra_content_table.html +3 -0
  77. nautobot/core/templates/components/panel/panel.html +16 -0
  78. nautobot/core/templates/components/panel/stats_panel_body.html +8 -0
  79. nautobot/core/templates/components/tab/content_wrapper.html +3 -0
  80. nautobot/core/templates/components/tab/label_wrapper.html +5 -0
  81. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +3 -0
  82. nautobot/core/templates/generic/object_retrieve.html +28 -17
  83. nautobot/core/templates/inc/computed_fields/panel_data.html +4 -7
  84. nautobot/core/templates/inc/custom_fields/panel.html +2 -2
  85. nautobot/core/templates/inc/custom_fields/panel_data.html +4 -7
  86. nautobot/core/templates/inc/footer.html +1 -0
  87. nautobot/core/templates/inc/media.html +0 -3
  88. nautobot/core/templates/inc/nav_menu.html +1 -1
  89. nautobot/core/templates/inc/relationships_panel.html +1 -1
  90. nautobot/core/templates/nautobot_config.py.j2 +3 -3
  91. nautobot/core/templates/panel_table.html +12 -0
  92. nautobot/core/templates/search.html +0 -7
  93. nautobot/core/templates/utilities/render_jinja2.html +117 -0
  94. nautobot/core/templatetags/helpers.py +101 -12
  95. nautobot/core/templatetags/ui_framework.py +40 -0
  96. nautobot/core/testing/api.py +23 -128
  97. nautobot/core/testing/context.py +18 -0
  98. nautobot/core/testing/filters.py +41 -58
  99. nautobot/core/testing/mixins.py +2 -7
  100. nautobot/core/testing/views.py +25 -123
  101. nautobot/core/tests/integration/test_app_home.py +1 -0
  102. nautobot/core/tests/integration/test_app_navbar.py +1 -0
  103. nautobot/core/tests/integration/test_filters.py +2 -0
  104. nautobot/core/tests/integration/test_home.py +1 -0
  105. nautobot/core/tests/integration/test_navbar.py +1 -0
  106. nautobot/core/tests/integration/test_view_authentication.py +1 -2
  107. nautobot/core/tests/nautobot_config.py +198 -0
  108. nautobot/core/tests/runner.py +3 -3
  109. nautobot/core/tests/test_api.py +82 -201
  110. nautobot/core/tests/test_csv.py +3 -25
  111. nautobot/core/tests/test_events.py +214 -0
  112. nautobot/core/tests/test_jinja_filters.py +1 -0
  113. nautobot/core/tests/test_jobs.py +84 -13
  114. nautobot/core/tests/test_navigations.py +7 -241
  115. nautobot/core/tests/test_templatetags_helpers.py +16 -0
  116. nautobot/core/tests/test_ui.py +150 -0
  117. nautobot/core/tests/test_utils.py +0 -25
  118. nautobot/core/tests/test_views.py +123 -31
  119. nautobot/core/ui/__init__.py +0 -0
  120. nautobot/core/ui/base.py +11 -0
  121. nautobot/core/ui/choices.py +44 -0
  122. nautobot/core/ui/homepage.py +167 -0
  123. nautobot/core/ui/nav.py +279 -0
  124. nautobot/core/ui/object_detail.py +1841 -0
  125. nautobot/core/ui/utils.py +36 -0
  126. nautobot/core/urls.py +4 -9
  127. nautobot/core/utils/config.py +30 -3
  128. nautobot/core/utils/lookup.py +20 -13
  129. nautobot/core/views/__init__.py +6 -1
  130. nautobot/core/views/generic.py +47 -52
  131. nautobot/core/views/mixins.py +15 -25
  132. nautobot/core/views/paginator.py +8 -5
  133. nautobot/core/views/renderers.py +3 -3
  134. nautobot/core/views/utils.py +11 -0
  135. nautobot/core/wsgi.py +3 -3
  136. nautobot/dcim/api/serializers.py +80 -179
  137. nautobot/dcim/api/urls.py +5 -0
  138. nautobot/dcim/api/views.py +17 -4
  139. nautobot/dcim/apps.py +1 -0
  140. nautobot/dcim/choices.py +28 -0
  141. nautobot/dcim/factory.py +58 -0
  142. nautobot/dcim/filters/__init__.py +197 -24
  143. nautobot/dcim/forms.py +203 -12
  144. nautobot/dcim/graphql/types.py +2 -2
  145. nautobot/dcim/migrations/0063_interfacevdcassignment_virtualdevicecontext_and_more.py +165 -0
  146. nautobot/dcim/migrations/0064_virtualdevicecontext_status_data_migration.py +28 -0
  147. nautobot/dcim/migrations/0065_controller_capabilities_and_more.py +29 -0
  148. nautobot/dcim/migrations/0066_controllermanageddevicegroup_radio_profiles_and_more.py +33 -0
  149. nautobot/dcim/models/__init__.py +4 -0
  150. nautobot/dcim/models/device_component_templates.py +2 -2
  151. nautobot/dcim/models/device_components.py +20 -22
  152. nautobot/dcim/models/devices.py +173 -4
  153. nautobot/dcim/models/locations.py +3 -3
  154. nautobot/dcim/models/power.py +5 -6
  155. nautobot/dcim/models/racks.py +6 -6
  156. nautobot/dcim/navigation.py +25 -224
  157. nautobot/dcim/signals.py +44 -0
  158. nautobot/dcim/tables/__init__.py +5 -3
  159. nautobot/dcim/tables/devices.py +96 -2
  160. nautobot/dcim/tables/devicetypes.py +2 -2
  161. nautobot/dcim/templates/dcim/controller/base.html +10 -0
  162. nautobot/dcim/templates/dcim/controller_create.html +1 -0
  163. nautobot/dcim/templates/dcim/controller_retrieve.html +5 -1
  164. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +25 -0
  165. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +66 -0
  166. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +46 -0
  167. nautobot/dcim/templates/dcim/device/base.html +6 -42
  168. nautobot/dcim/templates/dcim/device/wireless.html +73 -0
  169. nautobot/dcim/templates/dcim/device.html +3 -1
  170. nautobot/dcim/templates/dcim/interface.html +1 -0
  171. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  172. nautobot/dcim/templates/dcim/locationtype.html +0 -107
  173. nautobot/dcim/templates/dcim/locationtype_retrieve.html +8 -0
  174. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +76 -0
  175. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +34 -0
  176. nautobot/dcim/tests/test_api.py +172 -61
  177. nautobot/dcim/tests/test_filters.py +171 -109
  178. nautobot/dcim/tests/test_forms.py +2 -51
  179. nautobot/dcim/tests/test_graphql.py +0 -52
  180. nautobot/dcim/tests/test_models.py +126 -4
  181. nautobot/dcim/tests/test_signals.py +1 -0
  182. nautobot/dcim/tests/test_views.py +103 -11
  183. nautobot/dcim/urls.py +72 -27
  184. nautobot/dcim/utils.py +2 -2
  185. nautobot/dcim/views.py +369 -62
  186. nautobot/extras/api/customfields.py +2 -2
  187. nautobot/extras/api/serializers.py +91 -75
  188. nautobot/extras/api/urls.py +4 -0
  189. nautobot/extras/api/views.py +78 -15
  190. nautobot/extras/choices.py +13 -0
  191. nautobot/extras/constants.py +0 -1
  192. nautobot/extras/context_managers.py +23 -6
  193. nautobot/extras/datasources/git.py +4 -1
  194. nautobot/extras/factory.py +27 -0
  195. nautobot/extras/filters/__init__.py +59 -0
  196. nautobot/extras/forms/forms.py +125 -30
  197. nautobot/extras/forms/mixins.py +3 -11
  198. nautobot/extras/graphql/types.py +25 -1
  199. nautobot/extras/group_sync.py +3 -3
  200. nautobot/extras/health_checks.py +2 -1
  201. nautobot/extras/jobs.py +62 -26
  202. nautobot/extras/management/__init__.py +1 -0
  203. nautobot/extras/management/commands/runjob.py +7 -79
  204. nautobot/extras/management/commands/runjob_with_job_result.py +46 -0
  205. nautobot/extras/management/utils.py +87 -0
  206. nautobot/extras/managers.py +1 -3
  207. nautobot/extras/migrations/0018_joblog_data_migration.py +9 -7
  208. nautobot/extras/migrations/0117_create_job_queue_model.py +129 -0
  209. nautobot/extras/migrations/0118_task_queue_to_job_queue_migration.py +78 -0
  210. nautobot/extras/migrations/0119_remove_task_queues_from_job_and_queue_from_scheduled_job.py +28 -0
  211. nautobot/extras/models/__init__.py +4 -0
  212. nautobot/extras/models/change_logging.py +7 -3
  213. nautobot/extras/models/customfields.py +11 -12
  214. nautobot/extras/models/groups.py +9 -13
  215. nautobot/extras/models/jobs.py +218 -37
  216. nautobot/extras/models/models.py +2 -2
  217. nautobot/extras/models/relationships.py +69 -1
  218. nautobot/extras/models/secrets.py +5 -0
  219. nautobot/extras/navigation.py +20 -262
  220. nautobot/extras/plugins/__init__.py +56 -32
  221. nautobot/extras/plugins/marketplace_manifest.yml +450 -0
  222. nautobot/extras/plugins/urls.py +1 -0
  223. nautobot/extras/plugins/views.py +48 -1
  224. nautobot/extras/signals.py +39 -1
  225. nautobot/extras/tables.py +40 -6
  226. nautobot/extras/templates/extras/externalintegration_retrieve.html +0 -47
  227. nautobot/extras/templates/extras/inc/tags_panel.html +1 -5
  228. nautobot/extras/templates/extras/job_bulk_edit.html +2 -1
  229. nautobot/extras/templates/extras/job_detail.html +36 -6
  230. nautobot/extras/templates/extras/job_edit.html +5 -2
  231. nautobot/extras/templates/extras/job_list.html +2 -7
  232. nautobot/extras/templates/extras/jobqueue_retrieve.html +44 -0
  233. nautobot/extras/templates/extras/marketplace.html +278 -0
  234. nautobot/extras/templates/extras/plugins_list.html +35 -1
  235. nautobot/extras/templates/extras/plugins_tiles.html +79 -0
  236. nautobot/extras/templates/extras/role_retrieve.html +16 -0
  237. nautobot/extras/templates/extras/secret.html +0 -65
  238. nautobot/extras/templates/extras/secret_check.js +16 -0
  239. nautobot/extras/templates/extras/secret_create.html +114 -0
  240. nautobot/extras/templates/extras/secret_edit.html +1 -114
  241. nautobot/extras/templates/extras/secretsgroup_edit.html +1 -1
  242. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -0
  243. nautobot/extras/templatetags/job_buttons.py +5 -4
  244. nautobot/extras/templatetags/plugins.py +69 -6
  245. nautobot/extras/test_jobs/api_test_job.py +1 -1
  246. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  247. nautobot/extras/test_jobs/dry_run.py +1 -1
  248. nautobot/extras/test_jobs/fail.py +5 -5
  249. nautobot/extras/test_jobs/file_output.py +1 -1
  250. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  251. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  252. nautobot/extras/test_jobs/ipaddress_vars.py +1 -3
  253. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  254. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  255. nautobot/extras/test_jobs/log_redaction.py +1 -1
  256. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  257. nautobot/extras/test_jobs/modify_db.py +1 -1
  258. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  259. nautobot/extras/test_jobs/object_var_required.py +1 -1
  260. nautobot/extras/test_jobs/object_vars.py +1 -1
  261. nautobot/extras/test_jobs/pass.py +3 -3
  262. nautobot/extras/test_jobs/profiling.py +1 -1
  263. nautobot/extras/test_jobs/relative_import.py +3 -3
  264. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  265. nautobot/extras/test_jobs/task_queues.py +1 -1
  266. nautobot/extras/tests/integration/test_plugin_banner.py +2 -0
  267. nautobot/extras/tests/test_api.py +157 -55
  268. nautobot/extras/tests/test_context_managers.py +4 -1
  269. nautobot/extras/tests/test_customfields.py +1 -1
  270. nautobot/extras/tests/test_datasources.py +1 -2
  271. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  272. nautobot/extras/tests/test_filters.py +219 -535
  273. nautobot/extras/tests/test_forms.py +1 -20
  274. nautobot/extras/tests/test_job_variables.py +73 -152
  275. nautobot/extras/tests/test_jobs.py +43 -54
  276. nautobot/extras/tests/test_models.py +71 -16
  277. nautobot/extras/tests/test_relationships.py +5 -2
  278. nautobot/extras/tests/test_utils.py +23 -2
  279. nautobot/extras/tests/test_views.py +183 -43
  280. nautobot/extras/tests/test_webhooks.py +2 -1
  281. nautobot/extras/urls.py +2 -20
  282. nautobot/extras/utils.py +118 -4
  283. nautobot/extras/views.py +203 -92
  284. nautobot/extras/webhooks.py +5 -2
  285. nautobot/ipam/api/fields.py +3 -3
  286. nautobot/ipam/api/serializers.py +36 -137
  287. nautobot/ipam/api/views.py +93 -62
  288. nautobot/ipam/lookups.py +62 -101
  289. nautobot/ipam/models.py +11 -63
  290. nautobot/ipam/navigation.py +0 -90
  291. nautobot/ipam/querysets.py +2 -2
  292. nautobot/ipam/tables.py +6 -20
  293. nautobot/ipam/templates/ipam/routetarget.html +0 -28
  294. nautobot/ipam/templates/ipam/vrf.html +0 -47
  295. nautobot/ipam/tests/test_api.py +8 -419
  296. nautobot/ipam/tests/test_filters.py +39 -119
  297. nautobot/ipam/tests/test_forms.py +47 -51
  298. nautobot/ipam/tests/test_migrations.py +30 -30
  299. nautobot/ipam/tests/test_models.py +0 -41
  300. nautobot/ipam/tests/test_querysets.py +1 -63
  301. nautobot/ipam/urls.py +3 -69
  302. nautobot/ipam/utils/__init__.py +0 -24
  303. nautobot/ipam/views.py +153 -198
  304. nautobot/project-static/css/base.css +38 -3
  305. nautobot/project-static/docs/404.html +421 -19
  306. nautobot/project-static/docs/apps/index.html +421 -19
  307. nautobot/project-static/docs/apps/nautobot-apps.html +421 -19
  308. nautobot/project-static/docs/assets/extra.css +5 -1
  309. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.83f73b43.min.js} +2 -2
  310. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.83f73b43.min.js.map} +2 -2
  311. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +421 -19
  312. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +421 -19
  313. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +421 -172
  314. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +421 -19
  315. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +421 -19
  316. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +425 -21
  317. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +421 -19
  318. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +424 -22
  319. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9809 -0
  320. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +424 -63
  321. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +421 -19
  322. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +421 -19
  323. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +457 -20
  324. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +421 -19
  325. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +425 -25
  326. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +457 -19
  327. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +421 -19
  328. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +421 -19
  329. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +425 -215
  330. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +430 -342
  331. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5799 -1054
  332. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +421 -19
  333. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +447 -176
  334. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +460 -21
  335. nautobot/project-static/docs/development/apps/api/configuration-view.html +421 -19
  336. nautobot/project-static/docs/development/apps/api/database-backend-config.html +421 -19
  337. nautobot/project-static/docs/development/apps/api/models/django-admin.html +421 -19
  338. nautobot/project-static/docs/development/apps/api/models/global-search.html +421 -19
  339. nautobot/project-static/docs/development/apps/api/models/graphql.html +421 -19
  340. nautobot/project-static/docs/development/apps/api/models/index.html +421 -19
  341. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +421 -19
  342. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +421 -19
  343. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +421 -19
  344. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +421 -19
  345. nautobot/project-static/docs/development/apps/api/platform-features/index.html +421 -19
  346. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +421 -19
  347. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +421 -19
  348. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +421 -19
  349. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +421 -19
  350. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +424 -41
  351. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +421 -19
  352. nautobot/project-static/docs/development/apps/api/prometheus.html +421 -19
  353. nautobot/project-static/docs/development/apps/api/setup.html +425 -155
  354. nautobot/project-static/docs/development/apps/api/testing.html +421 -19
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +421 -19
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +421 -19
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +421 -19
  358. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +421 -19
  359. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +701 -130
  360. nautobot/project-static/docs/development/apps/api/views/base-template.html +421 -19
  361. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +421 -19
  362. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +421 -19
  363. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +421 -19
  364. nautobot/project-static/docs/development/apps/api/views/index.html +423 -20
  365. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +425 -19
  366. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +451 -19
  367. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +421 -19
  368. nautobot/project-static/docs/development/apps/api/views/notes.html +421 -19
  369. nautobot/project-static/docs/development/apps/api/views/rest-api.html +421 -19
  370. nautobot/project-static/docs/development/apps/api/views/urls.html +421 -19
  371. nautobot/project-static/docs/development/apps/index.html +421 -19
  372. nautobot/project-static/docs/development/apps/migration/code-updates.html +422 -52
  373. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +422 -20
  374. nautobot/project-static/docs/development/apps/migration/from-v1.html +421 -19
  375. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +421 -19
  376. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +421 -19
  377. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +421 -19
  378. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +424 -22
  379. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9219 -0
  380. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +9333 -0
  381. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +9474 -0
  382. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +9517 -0
  383. nautobot/project-static/docs/development/apps/porting-from-netbox.html +424 -22
  384. nautobot/project-static/docs/development/core/application-registry.html +421 -19
  385. nautobot/project-static/docs/development/core/best-practices.html +421 -19
  386. nautobot/project-static/docs/development/core/bootstrap-ui.html +421 -19
  387. nautobot/project-static/docs/development/core/caching.html +421 -19
  388. nautobot/project-static/docs/development/core/controllers.html +423 -19
  389. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +490 -45
  390. nautobot/project-static/docs/development/core/generic-views.html +421 -19
  391. nautobot/project-static/docs/development/core/getting-started.html +566 -179
  392. nautobot/project-static/docs/development/core/homepage.html +432 -30
  393. nautobot/project-static/docs/development/core/index.html +421 -19
  394. nautobot/project-static/docs/development/core/local-k8s.html +9453 -0
  395. nautobot/project-static/docs/development/core/model-checklist.html +424 -22
  396. nautobot/project-static/docs/development/core/model-features.html +421 -19
  397. nautobot/project-static/docs/development/core/natural-keys.html +421 -19
  398. nautobot/project-static/docs/development/core/navigation-menu.html +438 -26
  399. nautobot/project-static/docs/development/core/release-checklist.html +435 -45
  400. nautobot/project-static/docs/development/core/role-internals.html +421 -19
  401. nautobot/project-static/docs/development/core/settings.html +421 -19
  402. nautobot/project-static/docs/development/core/style-guide.html +421 -19
  403. nautobot/project-static/docs/development/core/templates.html +431 -22
  404. nautobot/project-static/docs/development/core/testing.html +421 -19
  405. nautobot/project-static/docs/development/core/ui-component-framework.html +11020 -0
  406. nautobot/project-static/docs/development/core/user-preferences.html +424 -22
  407. nautobot/project-static/docs/development/index.html +421 -19
  408. nautobot/project-static/docs/development/jobs/index.html +546 -160
  409. nautobot/project-static/docs/development/jobs/migration/from-v1.html +421 -19
  410. nautobot/project-static/docs/index.html +421 -19
  411. nautobot/project-static/docs/media/development/core/ui-component-framework/basic-panel-layout.png +0 -0
  412. nautobot/project-static/docs/media/development/core/ui-component-framework/button-example.png +0 -0
  413. nautobot/project-static/docs/media/development/core/ui-component-framework/dropdown-button-example.png +0 -0
  414. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-1.png +0 -0
  415. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-2.png +0 -0
  416. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example.png +0 -0
  417. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example.png +0 -0
  418. nautobot/project-static/docs/media/development/core/ui-component-framework/table-panels-family.png +0 -0
  419. nautobot/project-static/docs/media/development/core/ui-component-framework/text-panels-family.png +0 -0
  420. nautobot/project-static/docs/media/development/core/ui-component-framework/ui-framework-example.png +0 -0
  421. nautobot/project-static/docs/media/models/virtual_device_context_overview.drawio +73 -0
  422. nautobot/project-static/docs/media/models/virtual_device_context_overview.png +0 -0
  423. nautobot/project-static/docs/models/dcim/virtualdevicecontext.html +14 -0
  424. nautobot/project-static/docs/models/extras/jobqueue.html +14 -0
  425. nautobot/project-static/docs/models/wireless/radioprofile.html +14 -0
  426. nautobot/project-static/docs/models/wireless/supporteddatarate.html +14 -0
  427. nautobot/project-static/docs/models/wireless/wirelessnetwork.html +14 -0
  428. nautobot/project-static/docs/objects.inv +0 -0
  429. nautobot/project-static/docs/overview/application_stack.html +426 -20
  430. nautobot/project-static/docs/overview/design_philosophy.html +421 -19
  431. nautobot/project-static/docs/release-notes/index.html +445 -22
  432. nautobot/project-static/docs/release-notes/version-1.0.html +421 -19
  433. nautobot/project-static/docs/release-notes/version-1.1.html +421 -19
  434. nautobot/project-static/docs/release-notes/version-1.2.html +421 -19
  435. nautobot/project-static/docs/release-notes/version-1.3.html +421 -19
  436. nautobot/project-static/docs/release-notes/version-1.4.html +421 -19
  437. nautobot/project-static/docs/release-notes/version-1.5.html +421 -19
  438. nautobot/project-static/docs/release-notes/version-1.6.html +634 -667
  439. nautobot/project-static/docs/release-notes/version-2.0.html +421 -19
  440. nautobot/project-static/docs/release-notes/version-2.1.html +421 -19
  441. nautobot/project-static/docs/release-notes/version-2.2.html +421 -19
  442. nautobot/project-static/docs/release-notes/version-2.3.html +684 -886
  443. nautobot/project-static/docs/release-notes/version-2.4.html +10007 -0
  444. nautobot/project-static/docs/requirements.txt +2 -2
  445. nautobot/project-static/docs/search/search_index.json +1 -1
  446. nautobot/project-static/docs/sitemap.xml +334 -270
  447. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +421 -19
  449. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +421 -19
  450. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +423 -21
  451. nautobot/project-static/docs/user-guide/administration/configuration/index.html +433 -32
  452. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +421 -19
  453. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +765 -180
  454. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +421 -19
  455. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +421 -19
  456. nautobot/project-static/docs/user-guide/administration/guides/docker.html +434 -29
  457. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +421 -19
  458. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +421 -19
  459. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +421 -19
  460. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +421 -19
  461. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +421 -19
  462. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +421 -19
  463. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +421 -19
  464. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +421 -19
  465. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +421 -19
  466. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +421 -19
  467. nautobot/project-static/docs/user-guide/administration/installation/index.html +426 -20
  468. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +421 -19
  469. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +421 -19
  470. nautobot/project-static/docs/user-guide/administration/installation/services.html +421 -19
  471. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +421 -19
  472. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +442 -41
  473. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +435 -66
  474. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +435 -66
  475. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +421 -19
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +421 -19
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +421 -19
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +421 -19
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +421 -19
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +421 -19
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +421 -19
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-nautobot-app-location.yaml +0 -16
  483. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +421 -19
  484. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +427 -21
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +421 -19
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +421 -19
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +421 -19
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +421 -19
  489. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +421 -19
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +421 -19
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +421 -19
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +421 -19
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +421 -19
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +421 -19
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +421 -19
  496. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +421 -19
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +421 -19
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +421 -19
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +421 -19
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +421 -19
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +421 -19
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +457 -20
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +447 -22
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +421 -19
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +421 -19
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +421 -19
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +421 -19
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +421 -19
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +421 -19
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +421 -19
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +421 -19
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +421 -19
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +421 -19
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +421 -19
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +421 -19
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +421 -19
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +421 -19
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +421 -19
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +421 -19
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +421 -19
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +421 -19
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +421 -19
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +421 -19
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +421 -19
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +421 -19
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +421 -19
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +421 -19
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +421 -19
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +421 -19
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +421 -19
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +421 -19
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +421 -19
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +421 -19
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +421 -19
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +421 -19
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +421 -19
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +421 -19
  538. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9333 -0
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +424 -22
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +421 -19
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +421 -19
  542. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +421 -19
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +421 -19
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +421 -19
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +421 -19
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +421 -19
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +421 -19
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +421 -19
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +421 -19
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +421 -19
  551. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +421 -19
  552. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +421 -19
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +421 -19
  554. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +421 -19
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +421 -19
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +421 -19
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +421 -19
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +421 -19
  559. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +424 -22
  560. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9271 -0
  561. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9175 -0
  562. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9169 -0
  563. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9235 -0
  564. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +421 -19
  565. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +421 -19
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +421 -19
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +421 -19
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +421 -19
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +421 -19
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +421 -19
  571. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +421 -19
  572. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +421 -19
  573. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +421 -19
  574. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +421 -19
  575. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +421 -19
  576. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +421 -19
  577. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +421 -19
  578. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +421 -19
  579. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +421 -19
  580. nautobot/project-static/docs/user-guide/index.html +421 -19
  581. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +424 -22
  582. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +421 -19
  583. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +421 -19
  584. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +421 -19
  585. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +421 -19
  586. nautobot/project-static/docs/user-guide/platform-functionality/events.html +9575 -0
  587. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +424 -22
  588. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +421 -19
  589. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +421 -19
  590. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +421 -19
  591. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +421 -19
  592. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +421 -19
  593. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +426 -20
  594. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +424 -22
  595. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +421 -19
  596. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +421 -19
  597. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9182 -0
  598. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +424 -22
  599. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +421 -19
  600. nautobot/project-static/docs/user-guide/platform-functionality/note.html +421 -19
  601. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +421 -19
  602. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +424 -22
  603. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9250 -0
  604. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +421 -19
  605. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +421 -19
  606. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +424 -22
  607. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +421 -19
  608. nautobot/project-static/docs/user-guide/platform-functionality/role.html +421 -19
  609. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +421 -19
  610. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +421 -19
  611. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +424 -22
  612. nautobot/project-static/docs/user-guide/platform-functionality/status.html +421 -19
  613. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +421 -19
  614. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +489 -56
  615. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +421 -19
  616. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +421 -19
  617. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +421 -19
  618. nautobot/project-static/img/jinja_logo.svg +97 -0
  619. nautobot/project-static/js/forms.js +5 -0
  620. nautobot/project-static/js/nav_menu.js +2 -1
  621. nautobot/tenancy/api/serializers.py +0 -2
  622. nautobot/tenancy/factory.py +1 -1
  623. nautobot/tenancy/navigation.py +0 -29
  624. nautobot/tenancy/templates/tenancy/tenant.html +4 -91
  625. nautobot/tenancy/tests/test_filters.py +29 -134
  626. nautobot/tenancy/views.py +32 -23
  627. nautobot/users/admin.py +3 -1
  628. nautobot/users/api/serializers.py +4 -5
  629. nautobot/users/api/views.py +1 -1
  630. nautobot/users/forms.py +19 -0
  631. nautobot/users/templates/users/preferences.html +22 -0
  632. nautobot/users/tests/test_filters.py +1 -19
  633. nautobot/users/tests/test_views.py +57 -0
  634. nautobot/users/utils.py +8 -0
  635. nautobot/users/views.py +48 -11
  636. nautobot/virtualization/api/serializers.py +4 -4
  637. nautobot/virtualization/filters.py +2 -20
  638. nautobot/virtualization/navigation.py +0 -48
  639. nautobot/virtualization/templates/virtualization/clustertype.html +0 -39
  640. nautobot/virtualization/tests/test_filters.py +57 -183
  641. nautobot/virtualization/views.py +18 -15
  642. nautobot/wireless/__init__.py +0 -0
  643. nautobot/wireless/api/__init__.py +0 -0
  644. nautobot/wireless/api/serializers.py +44 -0
  645. nautobot/wireless/api/urls.py +20 -0
  646. nautobot/wireless/api/views.py +34 -0
  647. nautobot/wireless/apps.py +8 -0
  648. nautobot/wireless/choices.py +345 -0
  649. nautobot/wireless/factory.py +138 -0
  650. nautobot/wireless/filters.py +167 -0
  651. nautobot/wireless/forms.py +283 -0
  652. nautobot/wireless/homepage.py +19 -0
  653. nautobot/wireless/migrations/0001_initial.py +223 -0
  654. nautobot/wireless/migrations/__init__.py +0 -0
  655. nautobot/wireless/models.py +207 -0
  656. nautobot/wireless/navigation.py +105 -0
  657. nautobot/wireless/tables.py +244 -0
  658. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +81 -0
  659. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +26 -0
  660. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +88 -0
  661. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +64 -0
  662. nautobot/wireless/tests/__init__.py +0 -0
  663. nautobot/wireless/tests/test_api.py +247 -0
  664. nautobot/wireless/tests/test_filters.py +54 -0
  665. nautobot/wireless/tests/test_models.py +22 -0
  666. nautobot/wireless/tests/test_views.py +378 -0
  667. nautobot/wireless/urls.py +13 -0
  668. nautobot/wireless/views.py +129 -0
  669. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/METADATA +11 -14
  670. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/RECORD +674 -551
  671. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/WHEEL +1 -1
  672. nautobot/core/utils/navigation.py +0 -54
  673. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/LICENSE.txt +0 -0
  674. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/NOTICE +0 -0
  675. {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,20 @@
1
1
  from collections.abc import Mapping
2
2
  from datetime import datetime, timedelta
3
+ import json
3
4
  import logging
4
5
  from pathlib import Path
6
+ import sys
5
7
 
6
8
  from celery import current_app
9
+ from celery.beat import _evaluate_entry_args, _evaluate_entry_kwargs, reraise, SchedulingError
10
+ from celery.result import AsyncResult
7
11
  from django.conf import settings
8
12
  from django_celery_beat.schedulers import DatabaseScheduler, ModelEntry
9
13
  from kombu.utils.json import loads
10
14
 
11
- from nautobot.extras.models import ScheduledJob, ScheduledJobs
15
+ from nautobot.extras.choices import JobQueueTypeChoices
16
+ from nautobot.extras.models import JobResult, ScheduledJob, ScheduledJobs
17
+ from nautobot.extras.utils import run_kubernetes_job_and_return_job_result
12
18
 
13
19
  logger = logging.getLogger(__name__)
14
20
 
@@ -19,7 +25,7 @@ class NautobotScheduleEntry(ModelEntry):
19
25
  nautobot.extras.models.ScheduledJob model
20
26
  """
21
27
 
22
- def __init__(self, model, app=None): # pylint:disable=super-init-not-called # we must copy-and-paste from super
28
+ def __init__(self, model, app=None):
23
29
  """Initialize the model entry."""
24
30
  # copy-paste from django_celery_beat.schedulers
25
31
  self.app = app or current_app._get_current_object()
@@ -111,7 +117,46 @@ class NautobotDatabaseScheduler(DatabaseScheduler):
111
117
 
112
118
  Ref: https://github.com/celery/django-celery-beat/issues/558#issuecomment-1162730008
113
119
  """
114
- resp = super().apply_async(entry, producer=producer, advance=advance, **kwargs)
120
+ resp = None
121
+ entry = self.reserve(entry) if advance else entry
122
+ task = self.app.tasks.get(entry.task)
123
+
124
+ try:
125
+ entry_args = _evaluate_entry_args(entry.args)
126
+ entry_kwargs = _evaluate_entry_kwargs(entry.kwargs)
127
+ if task:
128
+ scheduled_job = entry.model
129
+ job_queue = scheduled_job.job_queue
130
+ # Distinguish between Celery and Kubernetes job queues
131
+ if job_queue.queue_type == JobQueueTypeChoices.TYPE_KUBERNETES:
132
+ job_result = JobResult.objects.create(
133
+ name=scheduled_job.job_model.name,
134
+ job_model=scheduled_job.job_model,
135
+ scheduled_job=scheduled_job,
136
+ user=scheduled_job.user,
137
+ task_name=scheduled_job.job_model.class_path,
138
+ celery_kwargs=entry.options,
139
+ )
140
+ job_result = run_kubernetes_job_and_return_job_result(
141
+ job_queue, job_result, json.dumps(entry_kwargs)
142
+ )
143
+ # Return an AsyncResult object to mimic the behavior of Celery tasks after the job is finished by Kubernetes Job Pod.
144
+ resp = AsyncResult(job_result.id)
145
+ else:
146
+ resp = task.apply_async(entry_args, entry_kwargs, producer=producer, **entry.options)
147
+ else:
148
+ resp = self.send_task(entry.task, entry_args, entry_kwargs, producer=producer, **entry.options)
149
+ except Exception as exc: # pylint: disable=broad-except
150
+ reraise(
151
+ SchedulingError,
152
+ SchedulingError(f"Couldn't apply scheduled task {entry.name}: {exc}"),
153
+ sys.exc_info()[2],
154
+ )
155
+ finally:
156
+ self._tasks_since_sync += 1
157
+ if self.should_sync():
158
+ self._do_sync()
159
+
115
160
  if entry.total_run_count != entry.model.total_run_count:
116
161
  entry.total_run_count = entry.model.total_run_count
117
162
  entry.model.save()
@@ -13,6 +13,7 @@ from django.core.management import CommandError, CommandParser, execute_from_com
13
13
  from django.core.management.utils import get_random_secret_key
14
14
  from jinja2 import BaseLoader, Environment
15
15
 
16
+ from nautobot.core.events import load_event_brokers
16
17
  from nautobot.core.settings_funcs import is_truthy
17
18
  from nautobot.extras.plugins.utils import load_plugins
18
19
 
@@ -44,6 +45,7 @@ def _preprocess_settings(settings, config_path):
44
45
  - Set up 'job_logs' database mirror
45
46
  - Handle our custom `STORAGE_BACKEND` setting.
46
47
  - Load plugins based on settings.PLUGINS (potentially affecting INSTALLED_APPS, MIDDLEWARE, and CONSTANCE_CONFIG)
48
+ - Load event brokers based on settings.EVENT_BROKERS
47
49
  """
48
50
  settings.SETTINGS_PATH = config_path
49
51
 
@@ -124,6 +126,12 @@ def _preprocess_settings(settings, config_path):
124
126
  # passed in.
125
127
  load_plugins(settings)
126
128
 
129
+ #
130
+ # Event Broker
131
+ #
132
+
133
+ load_event_brokers(settings.EVENT_BROKERS)
134
+
127
135
 
128
136
  def load_settings(config_path):
129
137
  """Load nautobot_config.py or its equivalent into memory as a `nautobot_config` pseudo-module."""
@@ -110,6 +110,10 @@ CONFIG_SETTING_SEPARATOR = ","
110
110
 
111
111
  CHARFIELD_MAX_LENGTH = 255
112
112
 
113
+ # Default values for pagination settings.
114
+ MAX_PAGE_SIZE_DEFAULT = 1000
115
+ PAGINATE_COUNT_DEFAULT = 50
116
+
113
117
  # Models excluded from the global search list
114
118
  GLOBAL_SEARCH_EXCLUDE_LIST = [
115
119
  "anotherexamplemodel",
@@ -146,12 +150,15 @@ GLOBAL_SEARCH_EXCLUDE_LIST = [
146
150
  "interfaceredundancygroup",
147
151
  "interfaceredundancygroupassociation",
148
152
  "interfacetemplate",
153
+ "interfacevdcassignment",
149
154
  "inventoryitem",
150
155
  "ipaddresstointerface",
151
156
  "job",
152
157
  "jobbutton",
153
158
  "jobhook",
154
159
  "joblogentry",
160
+ "jobqueue",
161
+ "jobqueueassignment",
155
162
  "jobresult",
156
163
  "locationtype",
157
164
  "manufacturer",
@@ -0,0 +1,116 @@
1
+ """Module providing for the publication of event notifications via mechanisms such as Redis, Kafka, syslog, etc."""
2
+
3
+ import fnmatch
4
+ import json
5
+ import logging
6
+
7
+ from django.utils.module_loading import import_string
8
+
9
+ from nautobot.core.celery import NautobotKombuJSONEncoder
10
+ from nautobot.core.events.exceptions import EventBrokerImproperlyConfigured, EventBrokerNotFound
11
+
12
+ from .base import EventBroker
13
+ from .redis_broker import RedisEventBroker
14
+ from .syslog_broker import SyslogEventBroker
15
+
16
+ _EVENT_BROKERS = []
17
+
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def load_event_brokers(event_broker_configs):
23
+ """Process plugins and log errors if they can't be loaded."""
24
+ for broker_name, broker in event_broker_configs.items():
25
+ options = broker.get("OPTIONS", {})
26
+ topics = broker.get("TOPICS", {})
27
+ if not isinstance(topics, dict):
28
+ raise EventBrokerImproperlyConfigured(
29
+ f"{broker_name} Malformed Event Broker Settings: Expected `TOPICS` to be a 'dict', instead a '{type(topics).__name__}' was provided"
30
+ )
31
+ include_topics = topics.get("INCLUDE")
32
+ if include_topics and not isinstance(include_topics, (list, tuple)):
33
+ raise EventBrokerImproperlyConfigured(
34
+ f"{broker_name} Malformed Event Broker Settings: Expected `INCLUDE` to be a 'list' or 'tuple', instead a '{type(include_topics).__name__}' was provided"
35
+ )
36
+ exclude_topics = topics.get("EXCLUDE", [])
37
+ if exclude_topics and not isinstance(exclude_topics, (list, tuple)):
38
+ raise EventBrokerImproperlyConfigured(
39
+ f"{broker_name} Malformed Event Broker Settings: Expected `EXCLUDE` to be a 'list' or 'tuple', instead a '{type(exclude_topics).__name__}' was provided"
40
+ )
41
+ options.update({"include_topics": include_topics, "exclude_topics": exclude_topics})
42
+
43
+ try:
44
+ event_broker_class = import_string(broker["CLASS"])
45
+ if not issubclass(event_broker_class, EventBroker):
46
+ raise EventBrokerImproperlyConfigured(
47
+ f"{broker_name} Malformed Event Broker Settings: Broker provided is not an EventBroker"
48
+ )
49
+ event_broker = event_broker_class(**options)
50
+ register_event_broker(event_broker)
51
+ except ImportError as err:
52
+ raise EventBrokerNotFound(f"Unable to import Event Broker {broker_name}.") from err
53
+
54
+
55
+ def register_event_broker(event_broker):
56
+ """
57
+ Register an `EventBroker` instance for use by Nautobot.
58
+
59
+ The `publish_event()` API will publish events to each registered broker.
60
+ The expectation/intent here, at least initially, is that a given deployment will instantiate zero or more
61
+ EventBrokers, then call `register_event_broker()` for each one, as part of Nautobot initial startup.
62
+
63
+ Args:
64
+ event_broker (EventBroker): The initialized/configured EventBroker instance to register.
65
+ """
66
+ if event_broker not in _EVENT_BROKERS:
67
+ _EVENT_BROKERS.append(event_broker)
68
+ logger.debug("Registered %s as an event broker", event_broker)
69
+ else:
70
+ logger.warning("Tried to register event broker %s but it was already registered", event_broker)
71
+
72
+
73
+ def deregister_event_broker(event_broker):
74
+ """
75
+ Deregister a previously registered `EventBroker` instance so that it no longer receives published events.
76
+ """
77
+ try:
78
+ _EVENT_BROKERS.remove(event_broker)
79
+ logger.debug("Deregistered event broker %s", event_broker)
80
+ except ValueError:
81
+ logger.warning("Tried to deregister event broker %s but it wasn't previously registered", event_broker)
82
+
83
+
84
+ def is_topic_match(topic, patterns):
85
+ return any(fnmatch.fnmatch(topic, pattern) for pattern in patterns)
86
+
87
+
88
+ def publish_event(*, topic, payload):
89
+ """Publish the given event payload to the given topic via all registered `EventBroker` instances.
90
+
91
+ Args:
92
+ topic (str): Topic identifier.
93
+ Convention is to use snake_case and use periods to delineate related groups of topics,
94
+ similar to Python logger naming. For example, you might use `nautobot.create.dcim.device`,
95
+ `nautobot.update.dcim.device`, `nautobot.delete.dcim.device`, `nautobot.create.dcim.interface`,
96
+ `nautobot.create.ipam.ipaddress`, etc.
97
+ payload (dict): JSON-serializable structured data to publish.
98
+ While not all EventBrokers may actually use JSON as their data format, it makes for a reasonable
99
+ lowest common denominator for serializability.
100
+ """
101
+ for event_broker in _EVENT_BROKERS:
102
+ exclude_topics = event_broker.exclude_topics
103
+ include_topics = event_broker.include_topics
104
+ if is_topic_match(topic, include_topics) and not is_topic_match(topic, exclude_topics):
105
+ serialized_payload = json.dumps(payload, cls=NautobotKombuJSONEncoder)
106
+ event_broker.publish(topic=topic, payload=serialized_payload)
107
+
108
+
109
+ __all__ = (
110
+ "deregister_event_broker",
111
+ "EventBroker",
112
+ "publish_event",
113
+ "RedisEventBroker",
114
+ "register_event_broker",
115
+ "SyslogEventBroker",
116
+ )
@@ -0,0 +1,27 @@
1
+ """Base classes for Nautobot event notification framework."""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ class EventBroker(ABC):
7
+ """Abstract base class for concrete implementations of event brokers such as syslog, Redis, Kafka, etc."""
8
+
9
+ def __init__(self, *args, include_topics=None, exclude_topics=None, **kwargs) -> None:
10
+ self.include_topics = include_topics or ["*"]
11
+ self.exclude_topics = exclude_topics or []
12
+ super().__init__(*args, **kwargs)
13
+
14
+ @abstractmethod
15
+ def publish(self, *, topic, payload):
16
+ """
17
+ Possibly publish the given data `payload` to the given event `topic`.
18
+
19
+ Args:
20
+ topic (str): Topic identifier.
21
+ Convention is to use snake_case and use periods to delineate related groups of topics,
22
+ similar to Python logger naming. For example, you might receive topics such as `create.dcim.device`,
23
+ `update.dcim.device`, `delete.dcim.device`, `create.dcim.interface`, `create.ipam.ipaddress`, etc.
24
+ payload (dict): JSON-serializable structured data to publish.
25
+ While not all EventBrokers may actually use JSON as their data format, it makes for a reasonable
26
+ lowest common denominator for serializability.
27
+ """
@@ -0,0 +1,10 @@
1
+ class EventBrokerError(Exception):
2
+ """Base exception for all event-broker-related errors."""
3
+
4
+
5
+ class EventBrokerNotFound(EventBrokerError):
6
+ """Raised when a specified event broker module cannot be found."""
7
+
8
+
9
+ class EventBrokerImproperlyConfigured(EventBrokerError):
10
+ """Raised when a event is not properly configured."""
@@ -0,0 +1,48 @@
1
+ """Simple EventBroker for publishing events to Redis.
2
+
3
+ To verify that an instance of this broker is working, you can do the following in `nautobot-server nbshell`:
4
+
5
+ ```python
6
+ import json
7
+
8
+ import redis
9
+
10
+
11
+ connection = redis.StrictRedis(host="redis", port=6379, db=2, password="...", charset="utf-8", decode_responses=True)
12
+
13
+
14
+ def listen():
15
+ sub = connection.pubsub()
16
+ sub.psubscribe("nautobot.*")
17
+ for message in sub.listen():
18
+ if message["type"] == "pmessage":
19
+ print(f"Got a pmessage on channel {message['channel']}")
20
+ print(json.dumps(json.loads(message["data"]), indent=2))
21
+
22
+
23
+ listen()
24
+ ```
25
+
26
+ Then perform any action that triggers an event, such as creating or updating a record through the Nautobot UI.
27
+ """
28
+
29
+ import redis
30
+
31
+ from .base import EventBroker
32
+
33
+
34
+ class RedisEventBroker(EventBroker):
35
+ """EventBroker for publishing events to Redis."""
36
+
37
+ def __init__(self, *args, url, **kwargs):
38
+ """Initialize and configure a RedisEventBroker.
39
+
40
+ Args:
41
+ url (str): The Redis URL to connect to.
42
+ """
43
+ self.url = url
44
+ self.connection = redis.StrictRedis.from_url(self.url)
45
+ super().__init__(*args, **kwargs)
46
+
47
+ def publish(self, *, topic, payload):
48
+ self.connection.publish(topic, payload)
@@ -0,0 +1,19 @@
1
+ """Simple EventBroker that just emits syslogs."""
2
+
3
+ import json
4
+ import logging
5
+
6
+ from .base import EventBroker
7
+
8
+
9
+ class SyslogEventBroker(EventBroker):
10
+ def __init__(self, *args, level=logging.INFO, **kwargs):
11
+ """Initialize a SyslogEventBroker that emits logs at the given level."""
12
+ super().__init__(*args, **kwargs)
13
+ self.level = level
14
+
15
+ def publish(self, *, topic, payload):
16
+ logger = logging.getLogger(f"nautobot.events.{topic}")
17
+ # Add an indent of 4 to payload
18
+ payload = json.dumps(json.loads(payload), indent=4)
19
+ logger.log(self.level, "%s", payload)
@@ -33,9 +33,3 @@ class FilterSetFieldNotFound(Exception):
33
33
  """
34
34
  An exception indicating that a filterset field could not be found.
35
35
  """
36
-
37
-
38
- class ViewConfigException(Exception):
39
- """
40
- An exception indicating that a detail view config is invalid.
41
- """
nautobot/core/filters.py CHANGED
@@ -618,7 +618,6 @@ class BaseFilterSet(django_filters.FilterSet):
618
618
  @staticmethod
619
619
  def _get_filter_lookup_dict(existing_filter):
620
620
  # Choose the lookup expression map based on the filter type
621
-
622
621
  if isinstance(
623
622
  existing_filter,
624
623
  (
@@ -638,7 +637,6 @@ class BaseFilterSet(django_filters.FilterSet):
638
637
  (
639
638
  django_filters.ModelChoiceFilter,
640
639
  django_filters.ModelMultipleChoiceFilter,
641
- MultiValueUUIDFilter,
642
640
  TagFilter,
643
641
  TreeNodeMultipleChoiceFilter,
644
642
  ),
@@ -668,7 +666,7 @@ class BaseFilterSet(django_filters.FilterSet):
668
666
  the form `<field_name>__<lookup_expr>`
669
667
  """
670
668
  magic_filters = {}
671
- if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in", "iexact"]:
669
+ if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in"]:
672
670
  return magic_filters
673
671
 
674
672
  # Choose the lookup expression map based on the filter type
@@ -679,7 +677,7 @@ class BaseFilterSet(django_filters.FilterSet):
679
677
 
680
678
  # Get properties of the existing filter for later use
681
679
  field_name = filter_field.field_name
682
- field = get_model_field(cls._meta.model, field_name) # pylint: disable=no-member
680
+ field = get_model_field(cls._meta.model, field_name)
683
681
 
684
682
  # If there isn't a model field, return.
685
683
  if field is None:
@@ -696,7 +694,7 @@ class BaseFilterSet(django_filters.FilterSet):
696
694
  new_filter_name = f"{filter_name}__{lookup_name}"
697
695
 
698
696
  try:
699
- if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}: # pylint: disable=no-member
697
+ if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}:
700
698
  # The filter field has been explicitly defined on the filterset class so we must manually
701
699
  # create the new filter with the same type because there is no guarantee the defined type
702
700
  # is the same as the default type for the field. This does not apply if the filter
@@ -727,10 +725,7 @@ class BaseFilterSet(django_filters.FilterSet):
727
725
  # If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
728
726
  # so we have to do it.
729
727
  if filter_field.label and filter_field.label != label_for_filter(
730
- cls._meta.model, # pylint: disable=no-member
731
- filter_field.field_name,
732
- filter_field.lookup_expr,
733
- filter_field.exclude,
728
+ cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
734
729
  ):
735
730
  # Lightly adjusted from label_for_filter() implementation:
736
731
  verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
@@ -753,22 +748,22 @@ class BaseFilterSet(django_filters.FilterSet):
753
748
  if not isinstance(new_filter_field, django_filters.Filter):
754
749
  raise TypeError(f"Tried to add filter ({new_filter_name}) which is not an instance of Django Filter")
755
750
 
756
- if new_filter_name in cls.base_filters: # pylint: disable=no-member
751
+ if new_filter_name in cls.base_filters:
757
752
  raise AttributeError(
758
753
  f"There was a conflict with filter `{new_filter_name}`, the custom filter was ignored."
759
754
  )
760
755
 
761
- cls.base_filters[new_filter_name] = new_filter_field # pylint: disable=no-member
756
+ cls.base_filters[new_filter_name] = new_filter_field
762
757
  # django-filters has no concept of "abstract" filtersets, so we have to fake it
763
- if cls._meta.model is not None: # pylint: disable=no-member
764
- cls.base_filters.update( # pylint: disable=no-member
758
+ if cls._meta.model is not None:
759
+ cls.base_filters.update(
765
760
  cls._generate_lookup_expression_filters(filter_name=new_filter_name, filter_field=new_filter_field)
766
761
  )
767
762
 
768
763
  @classmethod
769
764
  def get_fields(cls):
770
765
  fields = super().get_fields()
771
- if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude): # pylint: disable=no-member
766
+ if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude):
772
767
  # Add "id" as the first key in the `fields` dict
773
768
  fields = {"id": [django_filters.conf.settings.DEFAULT_LOOKUP_EXPR], **fields}
774
769
  return fields
@@ -785,7 +780,7 @@ class BaseFilterSet(django_filters.FilterSet):
785
780
  if filter_name.startswith("_"):
786
781
  del filters[filter_name]
787
782
 
788
- if getattr(cls._meta.model, "is_contact_associable_model", False): # pylint: disable=no-member
783
+ if getattr(cls._meta.model, "is_contact_associable_model", False):
789
784
  # Add "contacts" and "teams" filters
790
785
  from nautobot.extras.models import Contact, Team
791
786
 
@@ -805,13 +800,13 @@ class BaseFilterSet(django_filters.FilterSet):
805
800
  label="Teams (name or ID)",
806
801
  )
807
802
 
808
- if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False): # pylint: disable=no-member
809
- if not hasattr(cls._meta.model, "static_group_association_set"): # pylint: disable=no-member
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"):
810
805
  logger.warning(
811
806
  "Model %s has 'is_dynamic_group_associable_model = True' but lacks "
812
807
  "a 'static_group_association_set' attribute. Perhaps this is due to it inheriting from "
813
808
  "the deprecated DynamicGroupMixin class instead of the preferred DynamicGroupsModelMixin?",
814
- cls._meta.model, # pylint: disable=no-member
809
+ cls._meta.model,
815
810
  )
816
811
  else:
817
812
  # Add "dynamic_groups" field as the last key
@@ -821,14 +816,14 @@ class BaseFilterSet(django_filters.FilterSet):
821
816
  queryset=DynamicGroup.objects.all(),
822
817
  field_name="static_group_association_set__dynamic_group",
823
818
  to_field_name="name",
824
- query_params={"content_type": cls._meta.model._meta.label_lower}, # pylint: disable=no-member
819
+ query_params={"content_type": cls._meta.model._meta.label_lower},
825
820
  label="Dynamic groups (name or ID)",
826
821
  )
827
822
 
828
823
  # django-filters has no concept of "abstract" filtersets, so we have to fake it
829
- if cls._meta.model is not None: # pylint: disable=no-member
824
+ if cls._meta.model is not None:
830
825
  if "tags" in filters and isinstance(filters["tags"], TagFilter):
831
- filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]} # pylint: disable=no-member
826
+ filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]}
832
827
 
833
828
  new_filters = {}
834
829
  for existing_filter_name, existing_filter in filters.items():
@@ -0,0 +1,59 @@
1
+ [
2
+ {
3
+ "model": "users.user",
4
+ "pk": "90798182-9d19-479a-899f-a415fc2c62fe",
5
+ "fields": {
6
+ "password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
7
+ "last_login": null,
8
+ "is_superuser": false,
9
+ "username": "alice",
10
+ "first_name": "",
11
+ "last_name": "",
12
+ "email": "alice@localhost",
13
+ "is_staff": false,
14
+ "is_active": true,
15
+ "date_joined": "2021-05-25T23:49:55.008Z",
16
+ "config_data": {},
17
+ "groups": [],
18
+ "user_permissions": []
19
+ }
20
+ },
21
+ {
22
+ "model": "users.user",
23
+ "pk": "90798182-9d19-479a-899f-a415fc2c62fd",
24
+ "fields": {
25
+ "password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
26
+ "last_login": null,
27
+ "is_superuser": true,
28
+ "username": "bob",
29
+ "first_name": "",
30
+ "last_name": "",
31
+ "email": "bob@localhost",
32
+ "is_staff": true,
33
+ "is_active": true,
34
+ "date_joined": "2021-05-25T23:49:55.008Z",
35
+ "config_data": {},
36
+ "groups": [],
37
+ "user_permissions": []
38
+ }
39
+ },
40
+ {
41
+ "model": "users.user",
42
+ "pk": "90798182-9d19-479a-899f-a415fc2c62ff",
43
+ "fields": {
44
+ "password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
45
+ "last_login": null,
46
+ "is_superuser": false,
47
+ "username": "charlie",
48
+ "first_name": "",
49
+ "last_name": "",
50
+ "email": "charlie@localhost",
51
+ "is_staff": false,
52
+ "is_active": true,
53
+ "date_joined": "2021-05-25T23:49:55.008Z",
54
+ "config_data": {},
55
+ "groups": [],
56
+ "user_permissions": []
57
+ }
58
+ }
59
+ ]
@@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType
8
8
  from django.contrib.postgres.forms import SimpleArrayField
9
9
  from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
10
10
  from django.db.models import Q
11
- from django.forms.fields import BoundField, InvalidJSONInput, JSONField as _JSONField
11
+ from django.forms.fields import BoundField, CallableChoiceIterator, InvalidJSONInput, JSONField as _JSONField
12
12
  from django.templatetags.static import static
13
13
  from django.urls import reverse
14
14
  from django.utils.html import format_html
@@ -102,13 +102,13 @@ class CSVFileField(django_forms.FileField):
102
102
  "in double quotes."
103
103
  )
104
104
 
105
- def to_python(self, data):
105
+ def to_python(self, file):
106
106
  """For parity with CSVDataField, this returns the CSV text rather than an UploadedFile object."""
107
- if data is None:
107
+ if file is None:
108
108
  return None
109
109
 
110
- data = super().to_python(data)
111
- return data.read().decode("utf-8-sig").strip()
110
+ file = super().to_python(file)
111
+ return file.read().decode("utf-8-sig").strip()
112
112
 
113
113
 
114
114
  class CSVChoiceField(django_forms.ChoiceField):
@@ -660,11 +660,29 @@ class JSONArrayFormField(django_forms.JSONField):
660
660
  and each Array element is validated by `base_field` validators.
661
661
  """
662
662
 
663
- def __init__(self, base_field, *, delimiter=",", **kwargs):
663
+ def __init__(self, base_field, *, choices=None, delimiter=",", **kwargs):
664
+ self.has_choices = False
665
+ if choices:
666
+ self.choices = choices
667
+ self.widget = widgets.StaticSelect2Multiple(choices=choices)
668
+ self.has_choices = True
664
669
  self.base_field = base_field
665
670
  self.delimiter = delimiter
666
671
  super().__init__(**kwargs)
667
672
 
673
+ # TODO: change this when we upgrade to Django 5, it uses a getter/setter for choices
674
+ def _get_choices(self):
675
+ return getattr(self, "_choices", None)
676
+
677
+ def _set_choices(self, value):
678
+ if callable(value):
679
+ value = CallableChoiceIterator(value)
680
+ else:
681
+ value = list(value)
682
+ self._choices = self.widget.choices = value
683
+
684
+ choices = property(_get_choices, _set_choices)
685
+
668
686
  def clean(self, value):
669
687
  """
670
688
  Validate `value` and return its "cleaned" value as an appropriate
@@ -677,10 +695,21 @@ class JSONArrayFormField(django_forms.JSONField):
677
695
  """
678
696
  Return a string of this value.
679
697
  """
680
- if isinstance(value, list):
698
+ if self.has_choices:
699
+ if isinstance(value, list):
700
+ return value
701
+ return [value]
702
+ elif isinstance(value, list):
681
703
  return self.delimiter.join(str(self.base_field.prepare_value(v)) for v in value)
682
704
  return value
683
705
 
706
+ def bound_data(self, data, initial):
707
+ if data is None:
708
+ return None
709
+ if isinstance(data, list):
710
+ data = json.dumps(data)
711
+ return super().bound_data(data, initial)
712
+
684
713
  def to_python(self, value):
685
714
  """
686
715
  Convert `value` into JSON, raising django.core.exceptions.ValidationError
@@ -718,9 +747,25 @@ class JSONArrayFormField(django_forms.JSONField):
718
747
  self.base_field.validate(item)
719
748
  except ValidationError as error:
720
749
  errors.append(error)
750
+ if self.has_choices and not self.valid_value(item):
751
+ errors.append(ValidationError(f"{item} is not a valid choice"))
721
752
  if errors:
722
753
  raise ValidationError(errors)
723
754
 
755
+ def valid_value(self, value):
756
+ """Check to see if the provided value is a valid choice."""
757
+ text_value = str(value)
758
+ for k, v in self.choices:
759
+ if isinstance(v, (list, tuple)):
760
+ # This is an optgroup, so look inside the group for options
761
+ for k2, _ in v:
762
+ if value == k2 or text_value == str(k2):
763
+ return True
764
+ else:
765
+ if value == k or text_value == str(k):
766
+ return True
767
+ return False
768
+
724
769
  def run_validators(self, value):
725
770
  """
726
771
  Runs all validators against `value` and raise ValidationError if necessary.
@@ -774,7 +819,7 @@ class MultiMatchModelMultipleChoiceField(DynamicModelChoiceMixin, django_filters
774
819
  self.natural_key = kwargs.setdefault("to_field_name", "slug")
775
820
  super().__init__(*args, **kwargs)
776
821
 
777
- def _check_values(self, values): # pylint:disable=arguments-renamed
822
+ def _check_values(self, values):
778
823
  """
779
824
  This method overloads the grandparent method in `django.forms.models.ModelMultipleChoiceField`,
780
825
  re-using some of that method's existing logic and adding support for coupling this field with