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
@@ -41,6 +41,7 @@
41
41
  <a href="{% static 'docs/index.html' %}">Docs</a>
42
42
  {% endif %}
43
43
  &middot;
44
+ <img src="{% static 'img/jinja_logo.svg' %}" style="height:20px"> <a href="{% url 'render_jinja_template' %}">Jinja Renderer</a> &middot;
44
45
  <i class="mdi mdi-cloud-braces text-primary"></i> <a href="{% url 'api_docs' %}">API</a> &middot;
45
46
  <i class="mdi mdi-graphql text-primary"></i> <a href="{% url 'graphql' %}">GraphQL</a> &middot;
46
47
  <i class="mdi mdi-xml text-primary"></i> <a href="{{ settings.BRANDING_URLS.code }}">Code</a> &middot;
@@ -47,6 +47,3 @@
47
47
  <meta name="theme-color" content="#ffffff">
48
48
  <meta charset="UTF-8">
49
49
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
50
- {% if settings.PUBLISH_ROBOTS_TXT %}
51
- <meta name="robots" content="noindex, nofollow">
52
- {% endif %}
@@ -56,7 +56,7 @@
56
56
  {% endcomment %}
57
57
  {% url button_details.link as button_url %}
58
58
  {% if button_url %}
59
- <a href="{{ button_url }}"
59
+ <a href="{{ button_url }}{{ button_details.querystring }}"
60
60
  data-button-weight="{{ button_details.weight }}"
61
61
  class="btn btn-xs btn-{{ button_details.button_class }}"
62
62
  title="{{ button_title }}">
@@ -7,7 +7,7 @@
7
7
  <strong>Relationships</strong>
8
8
  </div>
9
9
  <table class="table table-hover panel-body attr-table">
10
- {% include 'inc/relationships_table_rows.html' with relationships_data=object.get_relationships_data %}
10
+ {% include 'inc/relationships_table_rows.html' with relationships_data=relationships %}
11
11
  </table>
12
12
  </div>
13
13
  {% endif %}
@@ -364,6 +364,9 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
364
364
  # if "NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY" in os.environ and os.environ["NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY"] != "":
365
365
  # DEVICE_NAME_AS_NATURAL_KEY = is_truthy(os.environ["NAUTOBOT_DEVICE_NAME_AS_NATURAL_KEY"])
366
366
 
367
+ # Event Brokers
368
+ # EVENT_BROKERS = {}
369
+
367
370
  # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
368
371
  # by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
369
372
  # Defaults to [].
@@ -490,9 +493,6 @@ INSTALLATION_METRICS_ENABLED = is_truthy(os.getenv("NAUTOBOT_INSTALLATION_METRIC
490
493
  # if "NAUTOBOT_PREFER_IPV4" in os.environ and os.environ["NAUTOBOT_PREFER_IPV4"] != "":
491
494
  # PREFER_IPV4 = is_truthy(os.environ["NAUTOBOT_PREFER_IPV4"])
492
495
 
493
- # Publish a simple "no-index" robots.txt for Nautobot?
494
- # PUBLISH_ROBOTS_TXT = is_truthy(os.getenv("NAUTOBOT_PUBLISH_ROBOTS_TXT", "True"))
495
-
496
496
  # Default height and width in pixels of a single rack unit in rendered rack elevations. Defaults are 22 and 230
497
497
  #
498
498
  # if (
@@ -1,4 +1,5 @@
1
1
  {% load render_table from django_tables2 %}
2
+ {% load helpers %}
2
3
 
3
4
  <div class="panel panel-{{ panel_class|default:'default' }}">
4
5
  {% if heading %}
@@ -13,4 +14,15 @@
13
14
  {% else %}
14
15
  <div class="panel-body text-muted">None</div>
15
16
  {% endif %}
17
+ {% if add_obj_url %}
18
+ <div class="panel-footer noprint">
19
+ <div class="pull-right">
20
+ <a href="{{ add_obj_url }}" class="btn btn-primary btn-xs">
21
+ <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add {{ table.data.data.model|meta:"verbose_name" }}
22
+ </a>
23
+ </div>
24
+
25
+ <div class="clearfix"></div>
26
+ </div>
27
+ {% endif %}
16
28
  </div>
@@ -65,10 +65,3 @@
65
65
  </div>
66
66
  {% endif %}
67
67
  {% endblock %}
68
-
69
- {% block javascript %}
70
- {{ block.super }}
71
- <script>
72
- var clipboard = new ClipboardJS('.btn');
73
- </script>
74
- {% endblock %}
@@ -0,0 +1,117 @@
1
+ {% extends "base.html" %}
2
+ {% load form_helpers %}
3
+ {% load helpers %}
4
+ {% load static %}
5
+
6
+ {% block extra_styles %}
7
+ <style type="text/css">
8
+ .button-container {
9
+ margin-bottom: 24px;
10
+ }
11
+ #render_jinja_template_form textarea {
12
+ resize: vertical;
13
+ white-space: nowrap;
14
+ }
15
+
16
+ </style>
17
+ {% endblock extra_styles %}
18
+
19
+ {% block header %}
20
+ <h1>{% block title %}Jinja Template Renderer{% endblock %}</h1>
21
+ <hr>
22
+ {% endblock %}
23
+
24
+
25
+
26
+ {% block content %}
27
+ <form class="form form-horizontal" onsubmit="handleFormSubmit(event)" id="render_jinja_template_form">
28
+ <div class="row">
29
+ <div class="col-lg-6 col-md-6">
30
+ <div class="panel panel-default">
31
+ <div class="panel-heading"><strong>Template</strong></div>
32
+ <div class="panel-body">
33
+ <div class="form-group">
34
+ <label class="col-md-3 control-label required" for="id_template_code">Jinja Template</label>
35
+ <div class="col-md-9">
36
+ <textarea cols="40" rows="10" class="form-control" id="id_template_code" required=""></textarea>
37
+ </div>
38
+ </div>
39
+ <div class="form-group">
40
+ <label class="col-md-3 control-label" for="id_context">Data</label>
41
+ <div class="col-md-9">
42
+ <textarea cols="40" rows="10" class="form-control" placeholder="{}" id="id_context">{}</textarea>
43
+ <span class="help-block">Enter data in <a href="https://json.org/">JSON</a> format.</span>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="button-container text-right">
49
+ <button type="submit" class="btn btn-primary">Render</button>
50
+ </div>
51
+ </div>
52
+ <div class="col-lg-6 col-md-6">
53
+ <div class="panel panel-default">
54
+ <div class="panel-heading">
55
+ <strong>Rendered Template</strong>
56
+ <button type="button" class="btn btn-inline btn-default copy-rendered-template" data-clipboard-target="#rendered_template">
57
+ <span class="mdi mdi-content-copy"></span>
58
+ </button>
59
+ </div>
60
+ <div class="panel-body">
61
+ <textarea readonly="readonly" cols="40" rows="23" class="form-control" placeholder="Rendered Template" id="rendered_template"></textarea>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </form>
67
+ {% endblock content %}
68
+
69
+ {% block javascript %}
70
+ {{ block.super }}
71
+ <script>
72
+ new ClipboardJS('.copy-rendered-template');
73
+ const sanitize = function(string) {
74
+ return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
75
+ };
76
+ async function handleFormSubmit(event) {
77
+ event.preventDefault(); // Prevent default form submission
78
+
79
+ // Check that the context is valid JSON
80
+ const context = document.getElementById("id_context");
81
+ var parsed_context;
82
+ try {
83
+ parsed_context = JSON.parse(context.value);
84
+ } catch (error) {
85
+ context.setCustomValidity("Invalid JSON");
86
+ context.reportValidity();
87
+ context.setCustomValidity("");
88
+ return error;
89
+ }
90
+
91
+ try {
92
+ const rendered_template = document.getElementById("rendered_template");
93
+ rendered_template.innerHTML = "Loading...";
94
+ const template_code = document.getElementById("id_template_code").value;
95
+ const url = "{% url 'core-api:render_jinja_template' %}";
96
+ const data = {
97
+ template_code: template_code,
98
+ context: parsed_context
99
+ }
100
+ const response = await fetch(url, {
101
+ method: "POST",
102
+ body: JSON.stringify(data),
103
+ headers: {"Content-Type": "application/json", "X-CSRFTOKEN": "{{ csrf_token }}"}
104
+ });
105
+ const responseData = await response.json();
106
+ if (!response.ok) {
107
+ const msg = responseData.detail ? responseData.detail : response.statusText;
108
+ rendered_template.innerHTML = sanitize(`An error occurred:\n\n${msg}`);
109
+ } else {
110
+ rendered_template.innerHTML = sanitize(responseData.rendered_template);
111
+ }
112
+ } catch (error) {
113
+ rendered_template.innerHTML = sanitize(`An error occurred:\n\n${error.message}`);
114
+ }
115
+ }
116
+ </script>
117
+ {% endblock javascript %}
@@ -21,6 +21,7 @@ import yaml
21
21
 
22
22
  from nautobot.apps.config import get_app_settings_or_config
23
23
  from nautobot.core import forms
24
+ from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
24
25
  from nautobot.core.utils import color, config, data, logging as nautobot_logging, lookup
25
26
  from nautobot.core.utils.requests import add_nautobot_version_query_param_to_url
26
27
 
@@ -118,6 +119,26 @@ def placeholder(value):
118
119
  return HTML_NONE
119
120
 
120
121
 
122
+ @library.filter()
123
+ @register.filter()
124
+ def pre_tag(value):
125
+ """Render a value within `<pre></pre>` tags to enable formatting.
126
+
127
+ Args:
128
+ value (any): Input value, can be any variable.
129
+
130
+ Returns:
131
+ (str): Value wrapped in `<pre></pre>` tags.
132
+
133
+ Example:
134
+ >>> pre_tag("")
135
+ '<pre></pre>'
136
+ >>> pre_tag("hello")
137
+ '<pre>hello</pre>'
138
+ """
139
+ return format_html("<pre>{}</pre>", value)
140
+
141
+
121
142
  @library.filter()
122
143
  @register.filter()
123
144
  def add_html_id(element_str, id_str):
@@ -202,6 +223,29 @@ def render_json(value, syntax_highlight=True, pretty_print=False):
202
223
 
203
224
  Unless `syntax_highlight=False` is specified, the returned string will be wrapped in a
204
225
  `<code class="language-json>` HTML tag to flag it for syntax highlighting by highlight.js.
226
+
227
+ Args:
228
+ value (any): Input value, can be any variable.
229
+ syntax_highlight (bool): Whether to highlight the JSON syntax or not.
230
+ pretty_print (bool): Wraps rendered and highlighted JSON in <pre> tag for better code display.
231
+
232
+ Returns:
233
+ (str): HTML
234
+ '<code class="language-json">{"json_key": "json_value"}</code>' if only syntax_highlight is True
235
+ - or -
236
+ '<pre><code class="language-json">{"json_key": "json_value"}</code></pre>' if both syntax_highlight and pretty_print are True
237
+ - or -
238
+ '{"json_key": "json_value"}' if only pretty_print is True (both syntax_highlight and pretty_print must be True for pretty print)
239
+
240
+ Examples:
241
+ >>> render_json({"key": "value"})
242
+ '<code class="language-json">{"key": "value"}</code>'
243
+ >>> render_json({"key": "value"}, syntax_highlight=False)
244
+ '{"key": "value"}'
245
+ >>> render_json({"key": "value"}, pretty_print=True)
246
+ '<pre><code class="language-json">{"key": "value"}</code></pre>'
247
+ >>> render_json({"key": "value"}, syntax_highlight=False, pretty_print=True)
248
+ '{"key": "value"}'
205
249
  """
206
250
  rendered_json = json.dumps(value, indent=4, sort_keys=True, ensure_ascii=False)
207
251
  if syntax_highlight:
@@ -616,6 +660,57 @@ def render_uptime(seconds):
616
660
  )
617
661
 
618
662
 
663
+ @library.filter()
664
+ @register.filter()
665
+ def dbm(value):
666
+ """Display value as dBm."""
667
+ return f"{value} dBm" if value else placeholder(value)
668
+
669
+
670
+ @library.filter()
671
+ @register.filter()
672
+ def hyperlinked_field(value, hyperlink=None):
673
+ """Render a value as a hyperlink."""
674
+ if not value:
675
+ return placeholder(value)
676
+ hyperlink = hyperlink or value
677
+ return format_html('<a href="{}">{}</a>', hyperlink, value)
678
+
679
+
680
+ @library.filter()
681
+ @register.filter()
682
+ def render_content_types(value):
683
+ """Render sorted by model and app_label ContentTypes value"""
684
+ if not value.exists():
685
+ return HTML_NONE
686
+ output = format_html("<ul>")
687
+ sorted_value = value.order_by("app_label", "model")
688
+ for content_type in sorted_value:
689
+ output += format_html("<li>{content_type}</li>", content_type=content_type)
690
+ output += format_html("</ul>")
691
+
692
+ return output
693
+
694
+
695
+ @library.filter()
696
+ @register.filter()
697
+ def render_ancestor_hierarchy(value):
698
+ """Renders a nested HTML list representing the hierarchy of ancestors for a given object."""
699
+ result = format_html('<ul class="tree-hierarchy">')
700
+ append_to_result = format_html("</ul>")
701
+ for ancestor in value.ancestors():
702
+ nestable_tag = format_html('<span title="nestable">↺</span>' if getattr(ancestor, "nestable", False) else "")
703
+ result += format_html(
704
+ "<li>{value} {nestable_tag}<ul>", value=hyperlinked_object(ancestor, "name"), nestable_tag=nestable_tag
705
+ )
706
+ append_to_result += format_html("</ul></li>")
707
+ nestable_tag = format_html('<span title="nestable">↺</span>' if getattr(value, "nestable", False) else "")
708
+ result += format_html("<li><strong>{value} {nestable_tag}</strong></li>", value=value, nestable_tag=nestable_tag)
709
+ result += append_to_result
710
+
711
+ return result
712
+
713
+
619
714
  #
620
715
  # Tags
621
716
  #
@@ -645,15 +740,15 @@ def querystring(request, **kwargs):
645
740
 
646
741
 
647
742
  @register.simple_tag()
648
- def table_config_button(table, table_name=None, extra_classes=""):
743
+ def table_config_button(table, table_name=None, extra_classes="", disabled=False):
649
744
  if table_name is None:
650
745
  table_name = table.__class__.__name__
651
746
  html_template = (
652
747
  '<button type="button" class="btn btn-default {}'
653
- '" data-toggle="modal" data-target="#{}_config" title="Configure table">'
748
+ '" data-toggle="modal" data-target="#{}_config" {} title="Configure table">'
654
749
  '<i class="mdi mdi-cog"></i> Configure</button>'
655
750
  )
656
- return format_html(html_template, extra_classes, table_name)
751
+ return format_html(html_template, extra_classes, table_name, 'disabled="disabled"' if disabled else "")
657
752
 
658
753
 
659
754
  @register.simple_tag()
@@ -795,13 +890,7 @@ def saved_view_modal(
795
890
  "clear_view",
796
891
  ]
797
892
 
798
- view_class = lookup.get_view_for_model(model, "List")
799
- table_name = None
800
- if hasattr(view_class, "table"):
801
- table_name = view_class.table.__name__
802
- if hasattr(view_class, "table_class"):
803
- table_name = view_class.table_class.__name__
804
-
893
+ table_name = lookup.get_table_for_model(model).__name__
805
894
  for param in non_filter_params:
806
895
  if param == "saved_view":
807
896
  current_saved_view_pk = filters_applied.pop(param, None)
@@ -852,11 +941,11 @@ def saved_view_modal(
852
941
  elif current_saved_view is not None and not per_page:
853
942
  # no changes made, display current saved view pagination count
854
943
  param_dict["per_page"] = current_saved_view.config.get(
855
- "pagination_count", config.get_settings_or_config("PAGINATE_COUNT")
944
+ "pagination_count", config.get_settings_or_config("PAGINATE_COUNT", fallback=PAGINATE_COUNT_DEFAULT)
856
945
  )
857
946
  else:
858
947
  # display default pagination count
859
- param_dict["per_page"] = config.get_settings_or_config("PAGINATE_COUNT")
948
+ param_dict["per_page"] = config.get_settings_or_config("PAGINATE_COUNT", fallback=PAGINATE_COUNT_DEFAULT)
860
949
 
861
950
  if sort_order:
862
951
  # user made changes to saved view sort order
@@ -0,0 +1,40 @@
1
+ from django import template
2
+ from django.utils.html import format_html_join
3
+
4
+ from nautobot.core.utils.lookup import get_view_for_model
5
+ from nautobot.core.views.utils import get_obj_from_context
6
+
7
+ register = template.Library()
8
+
9
+
10
+ @register.simple_tag(takes_context=True)
11
+ def render_tabs_labels(context, tabs):
12
+ """Render the tab labels for each Tab in the given `tabs` with the given `context`."""
13
+ if tabs is not None:
14
+ return format_html_join("\n", "{}", ([tab.render_label_wrapper(context)] for tab in tabs))
15
+ return ""
16
+
17
+
18
+ @register.simple_tag(takes_context=True)
19
+ def render_components(context, components):
20
+ """Render each component in the given `components` with the given `context`."""
21
+ if components is not None:
22
+ return format_html_join("\n", "{}", ([component.render(context)] for component in components))
23
+ return ""
24
+
25
+
26
+ @register.simple_tag(takes_context=True)
27
+ def render_detail_view_extra_buttons(context):
28
+ """
29
+ Render the "extra_buttons" if any from the base detail view associated with the context object.
30
+
31
+ This makes it possible for "extra" tabs (such as Changelog and Notes, and any added by App TemplateExtensions)
32
+ to automatically still render any `extra_buttons` defined by the base detail view, without the tab-specific views
33
+ needing to explicitly inherit from the base view.
34
+ """
35
+ obj = get_obj_from_context(context)
36
+ base_detail_view = get_view_for_model(obj)
37
+ object_detail_content = getattr(base_detail_view, "object_detail_content", None)
38
+ if object_detail_content is not None and object_detail_content.extra_buttons:
39
+ return render_components(context, object_detail_content.extra_buttons)
40
+ return ""
@@ -16,7 +16,6 @@ from rest_framework.test import APITransactionTestCase as _APITransactionTestCas
16
16
  from nautobot.core.api.utils import get_serializer_for_model
17
17
  from nautobot.core.models import fields as core_fields
18
18
  from nautobot.core.models.tree_queries import TreeModel
19
- from nautobot.core.templatetags.helpers import bettertitle
20
19
  from nautobot.core.testing import mixins, utils, views
21
20
  from nautobot.core.utils import lookup
22
21
  from nautobot.core.utils.data import is_uuid
@@ -169,6 +168,10 @@ class APIViewTestCases:
169
168
  if issubclass(self.model, extras_models.ChangeLoggedModel):
170
169
  self.assertIn("created", response.data)
171
170
  self.assertIn("last_updated", response.data)
171
+ if hasattr(self.model, "notes") and isinstance(instance1.notes, QuerySet):
172
+ self.assertIn("notes_url", response.data)
173
+ self.assertIn(f"{url}notes/", str(response.data["notes_url"]))
174
+ self.assertIn(instance1.get_notes_url(api=True), str(response.data["notes_url"]))
172
175
  # Fields that should be absent by default (opt-in fields):
173
176
  self.assertNotIn("computed_fields", response.data)
174
177
  self.assertNotIn("relationships", response.data)
@@ -218,58 +221,6 @@ class APIViewTestCases:
218
221
  response = self.client.options(url, **self.header)
219
222
  self.assertHttpStatus(response, status.HTTP_200_OK)
220
223
 
221
- with self.subTest("Assert Detail View Config is generated well"):
222
- # Namings Help
223
- # 1. detail_view_config: This is the detail view config set in the serializer.Meta.detail_view_config
224
- # 2. detail_view_schema: This is the retrieve schema generated from an OPTIONS request.
225
- # 3. advanced_view_schema: This is the advanced tab schema generated from an OPTIONS request.
226
- serializer = get_serializer_for_model(self._get_queryset().model)
227
- advanced_view_schema = response.data["view_options"]["retrieve"]["tabs"]["Advanced"]
228
-
229
- # Get default advanced tab fields
230
- self.assertEqual(len(advanced_view_schema), 1)
231
- self.assertIn("Object Details", advanced_view_schema[0])
232
- advanced_tab_fields = advanced_view_schema[0].get("Object Details")["fields"]
233
-
234
- if detail_view_config := getattr(serializer.Meta, "detail_view_config", None):
235
- detail_view_schema = response.data["view_options"]["retrieve"]["tabs"][
236
- bettertitle(self._get_queryset().model._meta.verbose_name)
237
- ]
238
- self.assertHttpStatus(response, status.HTTP_200_OK)
239
-
240
- # According to convention, fields in the advanced tab fields should not exist in
241
- # the `detail_view_schema`. Assert this is True.
242
- with self.subTest("Assert advanced tab fields should not exist in the detail_view_schema."):
243
- if detail_view_config.get("include_others"):
244
- # Handle "Other Fields" section specially as "Other Field" is dynamically added
245
- # by Nautobot and is not part of the serializer-defined detail_view_config
246
- other_fields = detail_view_schema[0]["Other Fields"]["fields"]
247
- for field in advanced_tab_fields:
248
- self.assertNotIn(field, other_fields)
249
-
250
- for col_idx, col in enumerate(detail_view_schema):
251
- for group_title, group in col.items():
252
- if group_title == "Other Fields":
253
- continue
254
- group_fields = group["fields"]
255
- # Config on the serializer
256
- if (
257
- col_idx < len(detail_view_config["layout"])
258
- and group_title in detail_view_config["layout"][col_idx]
259
- ):
260
- fields = detail_view_config["layout"][col_idx][group_title]["fields"]
261
- else:
262
- fields = []
263
-
264
- # Fields that are in the detail_view_schema must not be in the advanced tab as well
265
- for field in group_fields:
266
- self.assertNotIn(field, advanced_tab_fields)
267
-
268
- # Fields that are explicit in the detail_view_config must remain as such in the schema
269
- for field in fields:
270
- if field not in advanced_tab_fields:
271
- self.assertIn(field, group_fields)
272
-
273
224
  class ListObjectsViewTestCase(APITestCase):
274
225
  choices_fields = None
275
226
  filterset = None
@@ -637,10 +588,7 @@ class APIViewTestCases:
637
588
  POST a single object with permission.
638
589
  """
639
590
  # Add object-level permission
640
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
641
- obj_perm.save()
642
- obj_perm.users.add(self.user)
643
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
591
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
644
592
 
645
593
  initial_count = self._get_queryset().count()
646
594
  for i, create_data in enumerate(self.create_data):
@@ -683,10 +631,10 @@ class APIViewTestCases:
683
631
  self.fail("Couldn't find a single deletable object!")
684
632
 
685
633
  # Add object-level permission
686
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add", "view"])
687
- obj_perm.save()
688
- obj_perm.users.add(self.user)
689
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
634
+ self.add_permissions(
635
+ f"{self.model._meta.app_label}.add_{self.model._meta.model_name}",
636
+ f"{self.model._meta.app_label}.view_{self.model._meta.model_name}",
637
+ )
690
638
 
691
639
  response = self.client.get(self._get_detail_url(instance) + "?format=csv", **self.header)
692
640
  self.assertHttpStatus(response, status.HTTP_200_OK)
@@ -707,11 +655,7 @@ class APIViewTestCases:
707
655
  self.assertHttpStatus(response, status.HTTP_201_CREATED, csv_data)
708
656
  # Note that create via CSV is always treated as a bulk-create, and so the response is always a list of dicts
709
657
  new_instance = self._get_queryset().get(pk=response.data[0]["id"])
710
- if isinstance(orig_pk, int):
711
- self.assertNotEqual(new_instance.pk, orig_pk)
712
- else:
713
- # for our non-integer PKs, we're expecting the creation to respect the requested PK
714
- self.assertEqual(new_instance.pk, orig_pk)
658
+ self.assertNotEqual(new_instance.pk, orig_pk)
715
659
 
716
660
  new_serializer = serializer_class(new_instance, context={"request": None})
717
661
  new_data = new_serializer.data
@@ -739,10 +683,7 @@ class APIViewTestCases:
739
683
  POST a set of objects in a single request.
740
684
  """
741
685
  # Add object-level permission
742
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
743
- obj_perm.save()
744
- obj_perm.users.add(self.user)
745
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
686
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
746
687
 
747
688
  initial_count = self._get_queryset().count()
748
689
  response = self.client.post(self._get_list_url(), self.create_data, format="json", **self.header)
@@ -873,10 +814,10 @@ class APIViewTestCases:
873
814
  """GET and then PUT an object and verify that it's accepted and unchanged."""
874
815
  self.maxDiff = None
875
816
  # Add object-level permission
876
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["view", "change"])
877
- obj_perm.save()
878
- obj_perm.users.add(self.user)
879
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
817
+ self.add_permissions(
818
+ f"{self.model._meta.app_label}.view_{self.model._meta.model_name}",
819
+ f"{self.model._meta.app_label}.change_{self.model._meta.model_name}",
820
+ )
880
821
 
881
822
  instance = self._get_queryset().first()
882
823
  url = self._get_detail_url(instance)
@@ -907,10 +848,7 @@ class APIViewTestCases:
907
848
  self.skipTest("Bulk update data not set")
908
849
 
909
850
  # Add object-level permission
910
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
911
- obj_perm.save()
912
- obj_perm.users.add(self.user)
913
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
851
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
914
852
 
915
853
  id_list = list(self._get_queryset().values_list("id", flat=True)[:3])
916
854
  self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk update")
@@ -954,24 +892,11 @@ class APIViewTestCases:
954
892
  self.assertIn("actions", data)
955
893
 
956
894
  # Grab any field that has choices defined (fields with enums)
957
- if any(
958
- [
959
- "POST" in data["actions"],
960
- "PUT" in data["actions"],
961
- ]
962
- ):
963
- schema = data["schema"]
964
- props = schema["properties"]
965
- fields = props.keys()
966
- field_choices = set()
967
- for field_name in fields:
968
- obj = props[field_name]
969
- if "enum" in obj and "enumNames" in obj:
970
- enum = obj["enum"]
971
- # Zipping to assert that the enum and the mapping have the same number of items.
972
- model_field_choices = dict(zip(obj["enumNames"], enum))
973
- self.assertEqual(len(enum), len(model_field_choices))
974
- field_choices.add(field_name)
895
+ field_choices = {}
896
+ if "POST" in data["actions"]:
897
+ field_choices = {k for k, v in data["actions"]["POST"].items() if "choices" in v}
898
+ elif "PUT" in data["actions"]:
899
+ field_choices = {k for k, v in data["actions"]["PUT"].items() if "choices" in v}
975
900
  else:
976
901
  self.fail(f"Neither PUT nor POST are available actions in: {data['actions']}")
977
902
 
@@ -1026,10 +951,7 @@ class APIViewTestCases:
1026
951
  url = self._get_detail_url(instance)
1027
952
 
1028
953
  # Add object-level permission
1029
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1030
- obj_perm.save()
1031
- obj_perm.users.add(self.user)
1032
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
954
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
1033
955
 
1034
956
  response = self.client.delete(url, **self.header)
1035
957
  self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
@@ -1046,10 +968,7 @@ class APIViewTestCases:
1046
968
  """
1047
969
  id_list = self.get_deletable_object_pks()
1048
970
  # Add object-level permission
1049
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1050
- obj_perm.save()
1051
- obj_perm.users.add(self.user)
1052
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
971
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
1053
972
 
1054
973
  data = [{"id": id} for id in id_list]
1055
974
 
@@ -1061,30 +980,6 @@ class APIViewTestCases:
1061
980
  class NotesURLViewTestCase(APITestCase):
1062
981
  """Validate Notes URL on objects that have the Note model Mixin."""
1063
982
 
1064
- @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1065
- def test_notes_url_on_object(self):
1066
- if not hasattr(self.model, "notes"):
1067
- self.skipTest("Model doesn't appear to support Notes")
1068
- instance = self._get_queryset().first()
1069
- if not isinstance(instance.notes, QuerySet):
1070
- self.skipTest("Model has a notes field but it doesn't appear to be Notes")
1071
-
1072
- # Add object-level permission
1073
- obj_perm = users_models.ObjectPermission(
1074
- name="Test permission",
1075
- constraints={"pk": instance.pk},
1076
- actions=["view"],
1077
- )
1078
- obj_perm.save()
1079
- obj_perm.users.add(self.user)
1080
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1081
- url = self._get_detail_url(instance)
1082
- response = self.client.get(url, **self.header)
1083
- self.assertHttpStatus(response, status.HTTP_200_OK)
1084
- self.assertIn("notes_url", response.data)
1085
- self.assertIn(f"{url}notes/", str(response.data["notes_url"]))
1086
- self.assertIn(instance.get_notes_url(api=True), str(response.data["notes_url"]))
1087
-
1088
983
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1089
984
  def test_notes_url_functionality(self):
1090
985
  if not hasattr(self.model, "notes"):