nautobot 2.3.15b1__py3-none-any.whl → 2.4.0__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 (785) hide show
  1. nautobot/__init__.py +15 -0
  2. nautobot/apps/__init__.py +1 -1
  3. nautobot/apps/api.py +8 -10
  4. nautobot/apps/change_logging.py +2 -2
  5. nautobot/apps/choices.py +4 -4
  6. nautobot/apps/config.py +32 -3
  7. nautobot/apps/events.py +19 -0
  8. nautobot/apps/exceptions.py +0 -2
  9. nautobot/apps/factory.py +2 -2
  10. nautobot/apps/filters.py +1 -1
  11. nautobot/apps/forms.py +20 -20
  12. nautobot/apps/graphql.py +2 -2
  13. nautobot/apps/jobs.py +8 -8
  14. nautobot/apps/models.py +19 -19
  15. nautobot/apps/tables.py +1 -1
  16. nautobot/apps/testing.py +10 -10
  17. nautobot/apps/ui.py +44 -9
  18. nautobot/apps/utils.py +7 -15
  19. nautobot/apps/views.py +8 -6
  20. nautobot/circuits/api/serializers.py +1 -0
  21. nautobot/circuits/api/views.py +4 -8
  22. nautobot/circuits/navigation.py +0 -57
  23. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  24. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -71
  25. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -64
  26. nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +40 -0
  27. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +26 -0
  28. nautobot/circuits/templates/circuits/provider_retrieve.html +0 -76
  29. nautobot/circuits/tests/integration/test_relationships.py +33 -24
  30. nautobot/circuits/tests/test_filters.py +4 -8
  31. nautobot/circuits/views.py +143 -26
  32. nautobot/cloud/api/views.py +6 -10
  33. nautobot/cloud/factory.py +4 -1
  34. nautobot/cloud/models.py +1 -1
  35. nautobot/cloud/tests/test_filters.py +5 -4
  36. nautobot/cloud/views.py +0 -16
  37. nautobot/core/api/constants.py +11 -0
  38. nautobot/core/api/fields.py +5 -5
  39. nautobot/core/api/filter_backends.py +3 -9
  40. nautobot/core/api/metadata.py +28 -256
  41. nautobot/core/api/pagination.py +3 -2
  42. nautobot/core/api/renderers.py +3 -0
  43. nautobot/core/api/schema.py +13 -2
  44. nautobot/core/api/serializers.py +54 -268
  45. nautobot/core/api/urls.py +3 -4
  46. nautobot/core/api/utils.py +0 -62
  47. nautobot/core/api/views.py +102 -159
  48. nautobot/core/apps/__init__.py +22 -575
  49. nautobot/core/celery/__init__.py +13 -0
  50. nautobot/core/celery/schedulers.py +48 -3
  51. nautobot/core/choices.py +2 -2
  52. nautobot/core/cli/__init__.py +8 -0
  53. nautobot/core/constants.py +7 -0
  54. nautobot/core/events/__init__.py +116 -0
  55. nautobot/core/events/base.py +27 -0
  56. nautobot/core/events/exceptions.py +10 -0
  57. nautobot/core/events/redis_broker.py +48 -0
  58. nautobot/core/events/syslog_broker.py +19 -0
  59. nautobot/core/exceptions.py +0 -6
  60. nautobot/core/filters.py +19 -16
  61. nautobot/core/forms/__init__.py +19 -19
  62. nautobot/core/forms/fields.py +62 -14
  63. nautobot/core/forms/forms.py +33 -2
  64. nautobot/core/forms/utils.py +2 -1
  65. nautobot/core/graphql/schema.py +3 -1
  66. nautobot/core/graphql/types.py +1 -1
  67. nautobot/core/jobs/__init__.py +28 -7
  68. nautobot/core/jobs/bulk_actions.py +248 -0
  69. nautobot/core/jobs/cleanup.py +2 -2
  70. nautobot/core/jobs/groups.py +1 -1
  71. nautobot/core/management/commands/generate_test_data.py +21 -0
  72. nautobot/core/management/commands/validate_models.py +1 -1
  73. nautobot/core/middleware.py +16 -0
  74. nautobot/core/models/__init__.py +1 -1
  75. nautobot/core/models/fields.py +11 -7
  76. nautobot/core/models/query_functions.py +2 -2
  77. nautobot/core/models/tree_queries.py +2 -2
  78. nautobot/core/settings.py +71 -4
  79. nautobot/core/settings.yaml +107 -0
  80. nautobot/core/tables.py +15 -51
  81. nautobot/core/tasks.py +1 -1
  82. nautobot/core/templates/about.html +67 -0
  83. nautobot/core/templates/components/button/default.html +7 -0
  84. nautobot/core/templates/components/button/dropdown.html +20 -0
  85. nautobot/core/templates/components/layout/one_over_two.html +19 -0
  86. nautobot/core/templates/components/layout/two_over_one.html +19 -0
  87. nautobot/core/templates/components/panel/body_content_data_table.html +27 -0
  88. nautobot/core/templates/components/panel/body_content_objects_table.html +4 -0
  89. nautobot/core/templates/components/panel/body_content_tags.html +6 -0
  90. nautobot/core/templates/components/panel/body_content_text.html +12 -0
  91. nautobot/core/templates/components/panel/body_wrapper_generic.html +3 -0
  92. nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +3 -0
  93. nautobot/core/templates/components/panel/body_wrapper_table.html +3 -0
  94. nautobot/core/templates/components/panel/footer_contacts_table.html +20 -0
  95. nautobot/core/templates/components/panel/footer_content_table.html +14 -0
  96. nautobot/core/templates/components/panel/grouping_toggle.html +14 -0
  97. nautobot/core/templates/components/panel/header_extra_content_table.html +3 -0
  98. nautobot/core/templates/components/panel/panel.html +16 -0
  99. nautobot/core/templates/components/panel/stats_panel_body.html +8 -0
  100. nautobot/core/templates/components/tab/content_wrapper.html +3 -0
  101. nautobot/core/templates/components/tab/label_wrapper.html +5 -0
  102. nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +3 -0
  103. nautobot/core/templates/generic/object_retrieve.html +28 -17
  104. nautobot/core/templates/inc/computed_fields/panel_data.html +4 -7
  105. nautobot/core/templates/inc/custom_fields/panel.html +2 -2
  106. nautobot/core/templates/inc/custom_fields/panel_data.html +4 -7
  107. nautobot/core/templates/inc/footer.html +1 -0
  108. nautobot/core/templates/inc/media.html +3 -0
  109. nautobot/core/templates/inc/nav_menu.html +2 -1
  110. nautobot/core/templates/inc/relationships_panel.html +1 -1
  111. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  112. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  113. nautobot/core/templates/nautobot_config.py.j2 +16 -0
  114. nautobot/core/templates/panel_table.html +12 -0
  115. nautobot/core/templates/utilities/render_jinja2.html +117 -0
  116. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  117. nautobot/core/templates/utilities/theme_preview.html +7 -0
  118. nautobot/core/templatetags/helpers.py +104 -6
  119. nautobot/core/templatetags/ui_framework.py +40 -0
  120. nautobot/core/testing/__init__.py +8 -8
  121. nautobot/core/testing/api.py +187 -137
  122. nautobot/core/testing/context.py +18 -0
  123. nautobot/core/testing/filters.py +44 -34
  124. nautobot/core/testing/forms.py +2 -0
  125. nautobot/core/testing/views.py +68 -146
  126. nautobot/core/tests/integration/test_app_home.py +0 -1
  127. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  128. nautobot/core/tests/integration/test_filters.py +0 -2
  129. nautobot/core/tests/integration/test_home.py +0 -1
  130. nautobot/core/tests/integration/test_navbar.py +0 -1
  131. nautobot/core/tests/integration/test_view_authentication.py +2 -1
  132. nautobot/core/tests/nautobot_config.py +198 -0
  133. nautobot/core/tests/runner.py +3 -3
  134. nautobot/core/tests/test_api.py +154 -176
  135. nautobot/core/tests/test_events.py +214 -0
  136. nautobot/core/tests/test_forms.py +1 -0
  137. nautobot/core/tests/test_jinja_filters.py +1 -0
  138. nautobot/core/tests/test_jobs.py +387 -14
  139. nautobot/core/tests/test_navigations.py +7 -241
  140. nautobot/core/tests/test_settings_schema.py +7 -0
  141. nautobot/core/tests/test_tables.py +100 -0
  142. nautobot/core/tests/test_templatetags_helpers.py +16 -0
  143. nautobot/core/tests/test_ui.py +150 -0
  144. nautobot/core/tests/test_utils.py +55 -18
  145. nautobot/core/tests/test_views.py +153 -5
  146. nautobot/core/ui/__init__.py +0 -0
  147. nautobot/core/ui/base.py +11 -0
  148. nautobot/core/ui/choices.py +44 -0
  149. nautobot/core/ui/homepage.py +167 -0
  150. nautobot/core/ui/nav.py +280 -0
  151. nautobot/core/ui/object_detail.py +1855 -0
  152. nautobot/core/ui/utils.py +36 -0
  153. nautobot/core/urls.py +15 -0
  154. nautobot/core/utils/config.py +30 -3
  155. nautobot/core/utils/lookup.py +12 -2
  156. nautobot/core/utils/querysets.py +64 -0
  157. nautobot/core/utils/requests.py +24 -9
  158. nautobot/core/views/__init__.py +48 -1
  159. nautobot/core/views/generic.py +82 -177
  160. nautobot/core/views/mixins.py +98 -38
  161. nautobot/core/views/paginator.py +8 -5
  162. nautobot/core/views/renderers.py +9 -9
  163. nautobot/core/views/utils.py +11 -0
  164. nautobot/core/wsgi.py +3 -3
  165. nautobot/dcim/api/serializers.py +82 -189
  166. nautobot/dcim/api/urls.py +5 -0
  167. nautobot/dcim/api/views.py +57 -110
  168. nautobot/dcim/apps.py +1 -0
  169. nautobot/dcim/choices.py +28 -0
  170. nautobot/dcim/factory.py +58 -0
  171. nautobot/dcim/filters/__init__.py +204 -2
  172. nautobot/dcim/forms.py +221 -9
  173. nautobot/dcim/graphql/types.py +2 -2
  174. nautobot/dcim/migrations/0063_interfacevdcassignment_virtualdevicecontext_and_more.py +165 -0
  175. nautobot/dcim/migrations/0064_virtualdevicecontext_status_data_migration.py +28 -0
  176. nautobot/dcim/migrations/0065_controller_capabilities_and_more.py +29 -0
  177. nautobot/dcim/migrations/0066_controllermanageddevicegroup_radio_profiles_and_more.py +33 -0
  178. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  179. nautobot/dcim/models/__init__.py +5 -1
  180. nautobot/dcim/models/device_component_templates.py +2 -2
  181. nautobot/dcim/models/device_components.py +22 -20
  182. nautobot/dcim/models/devices.py +181 -3
  183. nautobot/dcim/models/locations.py +3 -3
  184. nautobot/dcim/models/power.py +6 -5
  185. nautobot/dcim/models/racks.py +6 -6
  186. nautobot/dcim/navigation.py +25 -224
  187. nautobot/dcim/signals.py +44 -0
  188. nautobot/dcim/tables/__init__.py +5 -3
  189. nautobot/dcim/tables/devices.py +103 -7
  190. nautobot/dcim/tables/devicetypes.py +2 -2
  191. nautobot/dcim/tables/racks.py +1 -1
  192. nautobot/dcim/templates/dcim/controller/base.html +10 -0
  193. nautobot/dcim/templates/dcim/controller_create.html +2 -7
  194. nautobot/dcim/templates/dcim/controller_retrieve.html +6 -10
  195. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +25 -0
  196. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -0
  197. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +51 -0
  198. nautobot/dcim/templates/dcim/device/base.html +6 -42
  199. nautobot/dcim/templates/dcim/device/wireless.html +73 -0
  200. nautobot/dcim/templates/dcim/device.html +4 -10
  201. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  202. nautobot/dcim/templates/dcim/interface.html +1 -0
  203. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  204. nautobot/dcim/templates/dcim/location.html +1 -9
  205. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  206. nautobot/dcim/templates/dcim/locationtype.html +0 -107
  207. nautobot/dcim/templates/dcim/locationtype_retrieve.html +8 -0
  208. nautobot/dcim/templates/dcim/rack.html +1 -9
  209. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  210. nautobot/dcim/templates/dcim/rackreservation.html +1 -9
  211. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +68 -0
  212. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +28 -0
  213. nautobot/dcim/tests/integration/test_controller.py +62 -0
  214. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  215. nautobot/dcim/tests/test_api.py +188 -64
  216. nautobot/dcim/tests/test_filters.py +172 -76
  217. nautobot/dcim/tests/test_graphql.py +52 -0
  218. nautobot/dcim/tests/test_jobs.py +118 -0
  219. nautobot/dcim/tests/test_models.py +159 -5
  220. nautobot/dcim/tests/test_signals.py +1 -0
  221. nautobot/dcim/tests/test_views.py +118 -88
  222. nautobot/dcim/urls.py +72 -27
  223. nautobot/dcim/utils.py +2 -2
  224. nautobot/dcim/views.py +357 -62
  225. nautobot/extras/api/customfields.py +2 -2
  226. nautobot/extras/api/serializers.py +111 -87
  227. nautobot/extras/api/urls.py +4 -0
  228. nautobot/extras/api/views.py +93 -35
  229. nautobot/extras/choices.py +13 -0
  230. nautobot/extras/constants.py +2 -1
  231. nautobot/extras/context_managers.py +23 -6
  232. nautobot/extras/datasources/git.py +4 -1
  233. nautobot/extras/factory.py +27 -0
  234. nautobot/extras/filters/__init__.py +66 -5
  235. nautobot/extras/forms/base.py +2 -2
  236. nautobot/extras/forms/forms.py +262 -59
  237. nautobot/extras/forms/mixins.py +3 -3
  238. nautobot/extras/graphql/types.py +25 -1
  239. nautobot/extras/health_checks.py +1 -2
  240. nautobot/extras/jobs.py +114 -20
  241. nautobot/extras/management/__init__.py +1 -0
  242. nautobot/extras/management/commands/runjob.py +7 -79
  243. nautobot/extras/management/commands/runjob_with_job_result.py +46 -0
  244. nautobot/extras/management/utils.py +87 -0
  245. nautobot/extras/managers.py +3 -1
  246. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  247. nautobot/extras/migrations/0117_create_job_queue_model.py +129 -0
  248. nautobot/extras/migrations/0118_task_queue_to_job_queue_migration.py +78 -0
  249. nautobot/extras/migrations/0119_remove_task_queues_from_job_and_queue_from_scheduled_job.py +28 -0
  250. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  251. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  252. nautobot/extras/models/__init__.py +5 -1
  253. nautobot/extras/models/change_logging.py +7 -3
  254. nautobot/extras/models/contacts.py +1 -1
  255. nautobot/extras/models/customfields.py +12 -11
  256. nautobot/extras/models/groups.py +11 -9
  257. nautobot/extras/models/jobs.py +237 -37
  258. nautobot/extras/models/models.py +2 -2
  259. nautobot/extras/models/relationships.py +69 -1
  260. nautobot/extras/models/secrets.py +5 -0
  261. nautobot/extras/navigation.py +20 -262
  262. nautobot/extras/plugins/__init__.py +54 -19
  263. nautobot/extras/plugins/marketplace_manifest.yml +455 -0
  264. nautobot/extras/plugins/tables.py +16 -14
  265. nautobot/extras/plugins/urls.py +1 -0
  266. nautobot/extras/plugins/views.py +104 -61
  267. nautobot/extras/registry.py +1 -1
  268. nautobot/extras/secrets/__init__.py +2 -2
  269. nautobot/extras/signals.py +39 -1
  270. nautobot/extras/tables.py +42 -6
  271. nautobot/extras/templates/extras/dynamicgroup.html +1 -9
  272. nautobot/extras/templates/extras/externalintegration_retrieve.html +0 -47
  273. nautobot/extras/templates/extras/inc/tags_panel.html +1 -5
  274. nautobot/extras/templates/extras/job_bulk_edit.html +2 -1
  275. nautobot/extras/templates/extras/job_detail.html +52 -6
  276. nautobot/extras/templates/extras/job_edit.html +6 -2
  277. nautobot/extras/templates/extras/job_list.html +2 -7
  278. nautobot/extras/templates/extras/jobqueue_retrieve.html +36 -0
  279. nautobot/extras/templates/extras/marketplace.html +296 -0
  280. nautobot/extras/templates/extras/plugin_detail.html +32 -15
  281. nautobot/extras/templates/extras/plugins_list.html +35 -1
  282. nautobot/extras/templates/extras/plugins_tiles.html +90 -0
  283. nautobot/extras/templates/extras/role_retrieve.html +16 -0
  284. nautobot/extras/templates/extras/secret.html +0 -65
  285. nautobot/extras/templates/extras/secret_check.js +16 -0
  286. nautobot/extras/templates/extras/secret_create.html +114 -0
  287. nautobot/extras/templates/extras/secret_edit.html +1 -114
  288. nautobot/extras/templates/extras/secretsgroup_edit.html +1 -1
  289. nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -0
  290. nautobot/extras/templatetags/job_buttons.py +5 -4
  291. nautobot/extras/templatetags/plugins.py +69 -6
  292. nautobot/extras/test_jobs/api_test_job.py +1 -1
  293. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  294. nautobot/extras/test_jobs/dry_run.py +1 -1
  295. nautobot/extras/test_jobs/fail.py +5 -5
  296. nautobot/extras/test_jobs/file_output.py +1 -1
  297. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  298. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  299. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  300. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  301. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  302. nautobot/extras/test_jobs/log_redaction.py +1 -1
  303. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  304. nautobot/extras/test_jobs/modify_db.py +1 -1
  305. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  306. nautobot/extras/test_jobs/object_var_required.py +1 -1
  307. nautobot/extras/test_jobs/object_vars.py +1 -1
  308. nautobot/extras/test_jobs/pass.py +3 -3
  309. nautobot/extras/test_jobs/profiling.py +1 -1
  310. nautobot/extras/test_jobs/relative_import.py +3 -3
  311. nautobot/extras/test_jobs/singleton.py +16 -0
  312. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  313. nautobot/extras/test_jobs/task_queues.py +1 -1
  314. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  315. nautobot/extras/tests/test_api.py +157 -55
  316. nautobot/extras/tests/test_context_managers.py +4 -1
  317. nautobot/extras/tests/test_customfields.py +1 -1
  318. nautobot/extras/tests/test_datasources.py +2 -1
  319. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  320. nautobot/extras/tests/test_filters.py +219 -535
  321. nautobot/extras/tests/test_forms.py +20 -1
  322. nautobot/extras/tests/test_job_variables.py +73 -152
  323. nautobot/extras/tests/test_jobs.py +192 -62
  324. nautobot/extras/tests/test_models.py +71 -16
  325. nautobot/extras/tests/test_plugins.py +62 -9
  326. nautobot/extras/tests/test_relationships.py +124 -10
  327. nautobot/extras/tests/test_utils.py +23 -2
  328. nautobot/extras/tests/test_views.py +162 -161
  329. nautobot/extras/tests/test_webhooks.py +2 -1
  330. nautobot/extras/urls.py +2 -20
  331. nautobot/extras/utils.py +119 -4
  332. nautobot/extras/views.py +188 -141
  333. nautobot/extras/webhooks.py +5 -2
  334. nautobot/ipam/api/fields.py +3 -3
  335. nautobot/ipam/api/serializers.py +44 -137
  336. nautobot/ipam/api/views.py +68 -110
  337. nautobot/ipam/factory.py +1 -1
  338. nautobot/ipam/filters.py +3 -2
  339. nautobot/ipam/models.py +10 -12
  340. nautobot/ipam/navigation.py +0 -90
  341. nautobot/ipam/querysets.py +2 -2
  342. nautobot/ipam/tables.py +3 -1
  343. nautobot/ipam/templates/ipam/ipaddress.html +1 -9
  344. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  345. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  346. nautobot/ipam/templates/ipam/prefix.html +1 -9
  347. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  348. nautobot/ipam/templates/ipam/routetarget.html +0 -28
  349. nautobot/ipam/templates/ipam/vlan.html +1 -9
  350. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  351. nautobot/ipam/templates/ipam/vrf.html +0 -47
  352. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  353. nautobot/ipam/tests/test_api.py +19 -6
  354. nautobot/ipam/tests/test_filters.py +39 -119
  355. nautobot/ipam/tests/test_forms.py +49 -47
  356. nautobot/ipam/tests/test_migrations.py +30 -30
  357. nautobot/ipam/tests/test_models.py +56 -36
  358. nautobot/ipam/tests/test_querysets.py +14 -0
  359. nautobot/ipam/tests/test_views.py +3 -0
  360. nautobot/ipam/urls.py +3 -69
  361. nautobot/ipam/utils/__init__.py +16 -10
  362. nautobot/ipam/views.py +91 -162
  363. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  364. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  365. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  366. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  367. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  368. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  369. nautobot/project-static/css/base.css +38 -3
  370. nautobot/project-static/docs/404.html +463 -19
  371. nautobot/project-static/docs/apps/index.html +463 -19
  372. nautobot/project-static/docs/apps/nautobot-apps.html +464 -21
  373. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  374. nautobot/project-static/docs/assets/extra.css +5 -1
  375. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
  376. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
  377. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +479 -25
  378. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +476 -22
  379. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +792 -291
  380. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +507 -33
  381. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +512 -36
  382. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +473 -22
  383. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +469 -20
  384. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +499 -35
  385. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9883 -0
  386. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +525 -77
  387. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +548 -53
  388. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +672 -96
  389. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1033 -180
  390. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +526 -51
  391. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +876 -190
  392. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +957 -237
  393. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +477 -23
  394. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +488 -30
  395. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +663 -101
  396. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +949 -481
  397. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +6427 -1236
  398. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +476 -22
  399. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +879 -346
  400. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +830 -173
  401. nautobot/project-static/docs/development/apps/api/configuration-view.html +463 -19
  402. nautobot/project-static/docs/development/apps/api/database-backend-config.html +463 -19
  403. nautobot/project-static/docs/development/apps/api/models/django-admin.html +463 -19
  404. nautobot/project-static/docs/development/apps/api/models/global-search.html +463 -19
  405. nautobot/project-static/docs/development/apps/api/models/graphql.html +463 -19
  406. nautobot/project-static/docs/development/apps/api/models/index.html +463 -19
  407. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +463 -19
  408. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +463 -19
  409. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +463 -19
  410. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +463 -19
  411. nautobot/project-static/docs/development/apps/api/platform-features/index.html +463 -19
  412. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +463 -19
  413. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +463 -19
  414. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +463 -19
  415. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +463 -19
  416. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +463 -19
  417. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +463 -19
  418. nautobot/project-static/docs/development/apps/api/prometheus.html +463 -19
  419. nautobot/project-static/docs/development/apps/api/setup.html +467 -155
  420. nautobot/project-static/docs/development/apps/api/testing.html +463 -19
  421. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +463 -19
  422. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +463 -19
  423. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +463 -19
  424. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +463 -19
  425. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +743 -130
  426. nautobot/project-static/docs/development/apps/api/views/base-template.html +463 -19
  427. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +463 -19
  428. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +463 -19
  429. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +463 -19
  430. nautobot/project-static/docs/development/apps/api/views/index.html +465 -20
  431. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +467 -19
  432. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +493 -19
  433. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +463 -19
  434. nautobot/project-static/docs/development/apps/api/views/notes.html +463 -19
  435. nautobot/project-static/docs/development/apps/api/views/rest-api.html +469 -21
  436. nautobot/project-static/docs/development/apps/api/views/urls.html +463 -19
  437. nautobot/project-static/docs/development/apps/index.html +463 -19
  438. nautobot/project-static/docs/development/apps/migration/code-updates.html +464 -52
  439. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +464 -20
  440. nautobot/project-static/docs/development/apps/migration/from-v1.html +463 -19
  441. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +463 -19
  442. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +463 -19
  443. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +463 -19
  444. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +466 -22
  445. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9261 -0
  446. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +9375 -0
  447. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +9671 -0
  448. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +9559 -0
  449. nautobot/project-static/docs/development/apps/porting-from-netbox.html +466 -22
  450. nautobot/project-static/docs/development/core/application-registry.html +463 -19
  451. nautobot/project-static/docs/development/core/best-practices.html +463 -19
  452. nautobot/project-static/docs/development/core/bootstrap-ui.html +463 -19
  453. nautobot/project-static/docs/development/core/caching.html +463 -19
  454. nautobot/project-static/docs/development/core/controllers.html +465 -19
  455. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +491 -90
  456. nautobot/project-static/docs/development/core/generic-views.html +463 -19
  457. nautobot/project-static/docs/development/core/getting-started.html +541 -129
  458. nautobot/project-static/docs/development/core/homepage.html +474 -30
  459. nautobot/project-static/docs/development/core/index.html +463 -19
  460. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9754 -0
  461. nautobot/project-static/docs/development/core/model-checklist.html +473 -27
  462. nautobot/project-static/docs/development/core/model-features.html +463 -19
  463. nautobot/project-static/docs/development/core/natural-keys.html +463 -19
  464. nautobot/project-static/docs/development/core/navigation-menu.html +480 -26
  465. nautobot/project-static/docs/development/core/release-checklist.html +480 -48
  466. nautobot/project-static/docs/development/core/role-internals.html +463 -19
  467. nautobot/project-static/docs/development/core/settings.html +463 -19
  468. nautobot/project-static/docs/development/core/style-guide.html +466 -22
  469. nautobot/project-static/docs/development/core/templates.html +473 -22
  470. nautobot/project-static/docs/development/core/testing.html +463 -19
  471. nautobot/project-static/docs/development/core/ui-component-framework.html +11116 -0
  472. nautobot/project-static/docs/development/core/user-preferences.html +466 -22
  473. nautobot/project-static/docs/development/index.html +463 -19
  474. nautobot/project-static/docs/development/jobs/index.html +501 -21
  475. nautobot/project-static/docs/development/jobs/migration/from-v1.html +463 -19
  476. nautobot/project-static/docs/index.html +471 -38
  477. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  478. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  479. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  480. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  481. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  482. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  483. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  484. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  485. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  486. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  487. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  488. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  489. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  490. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  491. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  492. nautobot/project-static/docs/media/development/core/ui-component-framework/basic-panel-layout.png +0 -0
  493. nautobot/project-static/docs/media/development/core/ui-component-framework/button-example.png +0 -0
  494. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  495. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  496. nautobot/project-static/docs/media/development/core/ui-component-framework/dropdown-button-example.png +0 -0
  497. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-1.png +0 -0
  498. nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-2.png +0 -0
  499. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example.png +0 -0
  500. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  501. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  502. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example.png +0 -0
  503. nautobot/project-static/docs/media/development/core/ui-component-framework/table-panels-family.png +0 -0
  504. nautobot/project-static/docs/media/development/core/ui-component-framework/text-panels-family.png +0 -0
  505. nautobot/project-static/docs/media/development/core/ui-component-framework/ui-framework-example.png +0 -0
  506. nautobot/project-static/docs/media/models/virtual_device_context_overview.drawio +73 -0
  507. nautobot/project-static/docs/media/models/virtual_device_context_overview.png +0 -0
  508. nautobot/project-static/docs/models/dcim/virtualdevicecontext.html +14 -0
  509. nautobot/project-static/docs/models/extras/jobqueue.html +14 -0
  510. nautobot/project-static/docs/models/wireless/radioprofile.html +14 -0
  511. nautobot/project-static/docs/models/wireless/supporteddatarate.html +14 -0
  512. nautobot/project-static/docs/models/wireless/wirelessnetwork.html +14 -0
  513. nautobot/project-static/docs/objects.inv +0 -0
  514. nautobot/project-static/docs/overview/application_stack.html +469 -23
  515. nautobot/project-static/docs/overview/design_philosophy.html +463 -19
  516. nautobot/project-static/docs/release-notes/index.html +485 -22
  517. nautobot/project-static/docs/release-notes/version-1.0.html +651 -208
  518. nautobot/project-static/docs/release-notes/version-1.1.html +648 -205
  519. nautobot/project-static/docs/release-notes/version-1.2.html +723 -280
  520. nautobot/project-static/docs/release-notes/version-1.3.html +749 -306
  521. nautobot/project-static/docs/release-notes/version-1.4.html +834 -392
  522. nautobot/project-static/docs/release-notes/version-1.5.html +1022 -581
  523. nautobot/project-static/docs/release-notes/version-1.6.html +942 -518
  524. nautobot/project-static/docs/release-notes/version-2.0.html +945 -504
  525. nautobot/project-static/docs/release-notes/version-2.1.html +780 -339
  526. nautobot/project-static/docs/release-notes/version-2.2.html +773 -332
  527. nautobot/project-static/docs/release-notes/version-2.3.html +1142 -448
  528. nautobot/project-static/docs/release-notes/version-2.4.html +10323 -0
  529. nautobot/project-static/docs/requirements.txt +2 -2
  530. nautobot/project-static/docs/search/search_index.json +1 -1
  531. nautobot/project-static/docs/sitemap.xml +342 -270
  532. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  533. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +463 -19
  534. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +463 -19
  535. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +463 -19
  536. nautobot/project-static/docs/user-guide/administration/configuration/index.html +475 -32
  537. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +463 -19
  538. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +894 -180
  539. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +463 -19
  540. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +463 -19
  541. nautobot/project-static/docs/user-guide/administration/guides/docker.html +476 -29
  542. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +463 -19
  543. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +463 -19
  544. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +463 -19
  545. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +463 -19
  546. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +463 -19
  547. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +465 -21
  548. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +463 -19
  549. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +463 -19
  550. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +463 -19
  551. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +483 -23
  552. nautobot/project-static/docs/user-guide/administration/installation/index.html +468 -20
  553. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +464 -20
  554. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +463 -19
  555. nautobot/project-static/docs/user-guide/administration/installation/services.html +463 -19
  556. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +463 -19
  557. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +484 -41
  558. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +477 -66
  559. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +477 -66
  560. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +463 -19
  561. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +463 -19
  562. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +463 -19
  563. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +463 -19
  564. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +463 -19
  565. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +466 -23
  566. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +463 -19
  567. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-nautobot-app-location.yaml +0 -16
  568. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +463 -19
  569. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +469 -21
  570. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +463 -19
  571. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +463 -19
  572. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +463 -19
  573. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +463 -19
  574. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +463 -19
  575. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +463 -19
  576. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +463 -19
  577. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +463 -19
  578. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +463 -19
  579. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +463 -19
  580. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +463 -19
  581. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +463 -19
  582. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +463 -19
  583. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +463 -19
  584. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +463 -19
  585. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +463 -19
  586. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +463 -19
  587. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +499 -20
  588. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +489 -22
  589. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +463 -19
  590. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +463 -19
  591. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +463 -19
  592. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +463 -19
  593. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +463 -19
  594. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +463 -19
  595. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +463 -19
  596. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +463 -19
  597. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +463 -19
  598. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +463 -19
  599. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +463 -19
  600. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +463 -19
  601. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +463 -19
  602. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +463 -19
  603. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +463 -19
  604. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +463 -19
  605. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +463 -19
  606. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +463 -19
  607. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +463 -19
  608. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +463 -19
  609. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +463 -19
  610. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +463 -19
  611. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +463 -19
  612. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +463 -19
  613. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +463 -19
  614. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +463 -19
  615. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +463 -19
  616. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +463 -19
  617. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +463 -19
  618. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +463 -19
  619. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +463 -19
  620. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +463 -19
  621. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +463 -19
  622. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +463 -19
  623. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9375 -0
  624. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +470 -30
  625. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +463 -19
  626. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +463 -19
  627. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +463 -19
  628. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +463 -19
  629. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +463 -19
  630. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +463 -19
  631. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +463 -19
  632. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +463 -19
  633. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +463 -19
  634. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +463 -19
  635. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +463 -19
  636. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +463 -19
  637. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +466 -22
  638. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +463 -19
  639. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +463 -19
  640. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +463 -19
  641. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +463 -19
  642. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +463 -19
  643. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +463 -19
  644. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +466 -22
  645. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9313 -0
  646. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9217 -0
  647. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9211 -0
  648. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9277 -0
  649. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +463 -19
  650. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +463 -19
  651. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +463 -19
  652. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +463 -19
  653. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +463 -19
  654. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +463 -19
  655. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +463 -19
  656. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +463 -19
  657. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +463 -19
  658. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +463 -19
  659. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +463 -19
  660. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +468 -22
  661. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +463 -19
  662. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  663. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  664. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  665. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  666. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  667. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  668. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  669. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  670. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  671. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  672. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  673. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  674. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  675. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +463 -19
  676. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +463 -19
  677. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +466 -22
  678. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
  679. nautobot/project-static/docs/user-guide/index.html +463 -19
  680. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +466 -22
  681. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +467 -23
  682. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +463 -19
  683. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +463 -19
  684. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +463 -19
  685. nautobot/project-static/docs/user-guide/platform-functionality/events.html +9617 -0
  686. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +466 -22
  687. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +463 -19
  688. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +463 -19
  689. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +463 -19
  690. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +463 -19
  691. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +463 -19
  692. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +472 -23
  693. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +466 -22
  694. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +466 -22
  695. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +463 -19
  696. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9224 -0
  697. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
  698. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +466 -22
  699. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +463 -19
  700. nautobot/project-static/docs/user-guide/platform-functionality/note.html +463 -19
  701. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +463 -19
  702. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +467 -23
  703. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9292 -0
  704. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +463 -19
  705. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +511 -40
  706. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +494 -23
  707. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +463 -19
  708. nautobot/project-static/docs/user-guide/platform-functionality/role.html +463 -19
  709. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +463 -19
  710. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +463 -19
  711. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +466 -22
  712. nautobot/project-static/docs/user-guide/platform-functionality/status.html +463 -19
  713. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +463 -19
  714. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +531 -56
  715. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +463 -19
  716. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +463 -19
  717. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +463 -19
  718. nautobot/project-static/img/jinja_logo.svg +97 -0
  719. nautobot/project-static/js/forms.js +6 -1
  720. nautobot/project-static/js/nav_menu.js +2 -1
  721. nautobot/tenancy/api/serializers.py +0 -2
  722. nautobot/tenancy/api/views.py +9 -13
  723. nautobot/tenancy/factory.py +1 -1
  724. nautobot/tenancy/navigation.py +0 -29
  725. nautobot/tenancy/templates/tenancy/tenant.html +4 -91
  726. nautobot/tenancy/tests/test_filters.py +29 -134
  727. nautobot/tenancy/views.py +35 -24
  728. nautobot/users/admin.py +3 -1
  729. nautobot/users/api/serializers.py +4 -4
  730. nautobot/users/api/views.py +3 -3
  731. nautobot/users/forms.py +19 -0
  732. nautobot/users/templates/users/preferences.html +22 -0
  733. nautobot/users/tests/test_filters.py +1 -19
  734. nautobot/users/tests/test_views.py +57 -0
  735. nautobot/users/utils.py +8 -0
  736. nautobot/users/views.py +48 -11
  737. nautobot/virtualization/api/serializers.py +4 -4
  738. nautobot/virtualization/api/views.py +5 -24
  739. nautobot/virtualization/filters.py +1 -2
  740. nautobot/virtualization/models.py +1 -1
  741. nautobot/virtualization/navigation.py +0 -48
  742. nautobot/virtualization/tables.py +2 -2
  743. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  744. nautobot/virtualization/templates/virtualization/clustertype.html +0 -39
  745. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
  746. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  747. nautobot/virtualization/tests/test_filters.py +57 -166
  748. nautobot/virtualization/views.py +18 -15
  749. nautobot/wireless/__init__.py +0 -0
  750. nautobot/wireless/api/__init__.py +0 -0
  751. nautobot/wireless/api/serializers.py +44 -0
  752. nautobot/wireless/api/urls.py +20 -0
  753. nautobot/wireless/api/views.py +34 -0
  754. nautobot/wireless/apps.py +8 -0
  755. nautobot/wireless/choices.py +345 -0
  756. nautobot/wireless/factory.py +138 -0
  757. nautobot/wireless/filters.py +167 -0
  758. nautobot/wireless/forms.py +283 -0
  759. nautobot/wireless/homepage.py +19 -0
  760. nautobot/wireless/migrations/0001_initial.py +223 -0
  761. nautobot/wireless/migrations/__init__.py +0 -0
  762. nautobot/wireless/models.py +207 -0
  763. nautobot/wireless/navigation.py +105 -0
  764. nautobot/wireless/tables.py +244 -0
  765. nautobot/wireless/templates/wireless/radioprofile_retrieve.html +81 -0
  766. nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +26 -0
  767. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +88 -0
  768. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +56 -0
  769. nautobot/wireless/tests/__init__.py +0 -0
  770. nautobot/wireless/tests/integration/__init__.py +0 -0
  771. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  772. nautobot/wireless/tests/test_api.py +247 -0
  773. nautobot/wireless/tests/test_filters.py +82 -0
  774. nautobot/wireless/tests/test_models.py +22 -0
  775. nautobot/wireless/tests/test_views.py +378 -0
  776. nautobot/wireless/urls.py +13 -0
  777. nautobot/wireless/views.py +119 -0
  778. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/METADATA +11 -13
  779. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/RECORD +783 -613
  780. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/WHEEL +1 -1
  781. nautobot/core/fixtures/user-data.json +0 -59
  782. nautobot/core/utils/navigation.py +0 -54
  783. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/LICENSE.txt +0 -0
  784. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/NOTICE +0 -0
  785. {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,12 +1,14 @@
1
1
  import random
2
2
  import string
3
+ from typing import ClassVar, Iterable
3
4
 
4
5
  from django.contrib.contenttypes.models import ContentType
5
- from django.db.models import Count, Q
6
+ from django.db.models import Count, Q, QuerySet
6
7
  from django.db.models.fields import CharField, TextField
7
8
  from django.db.models.fields.related import ManyToManyField
8
9
  from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
9
10
  from django.test import tag
11
+ from django_filters import FilterSet
10
12
 
11
13
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
12
14
  from nautobot.core.filters import (
@@ -18,6 +20,7 @@ from nautobot.core.filters import (
18
20
  )
19
21
  from nautobot.core.models.generics import PrimaryModel
20
22
  from nautobot.core.testing import views
23
+ from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
21
24
  from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Tag, Team
22
25
  from nautobot.tenancy import models
23
26
 
@@ -27,6 +30,8 @@ class FilterTestCases:
27
30
  class BaseFilterTestCase(views.TestCase):
28
31
  """Base class for testing of FilterSets."""
29
32
 
33
+ queryset: ClassVar[QuerySet]
34
+
30
35
  def get_filterset_test_values(self, field_name, queryset=None):
31
36
  """Returns a list of distinct values from the requested queryset field to use in filterset tests.
32
37
 
@@ -68,8 +73,7 @@ class FilterTestCases:
68
73
  class FilterTestCase(BaseFilterTestCase):
69
74
  """Add common tests for all FilterSets."""
70
75
 
71
- queryset = None
72
- filterset = None
76
+ filterset: ClassVar[type[FilterSet]]
73
77
 
74
78
  # filter predicate fields that should be excluded from q test case
75
79
  exclude_q_filter_predicates = []
@@ -81,7 +85,13 @@ class FilterTestCases:
81
85
  # ["filter1"],
82
86
  # ["filter2", "field2__name"],
83
87
  # ]
84
- generic_filter_tests = []
88
+ generic_filter_tests: ClassVar[Iterable]
89
+
90
+ def setUp(self):
91
+ for attr in ["queryset", "filterset", "generic_filter_tests"]:
92
+ if not hasattr(self, attr):
93
+ raise NotImplementedError(f'{self} is missing a value for required attribute "{attr}"')
94
+ super().setUp()
85
95
 
86
96
  def get_q_filter(self):
87
97
  """Helper method to return q filter."""
@@ -137,15 +147,22 @@ class FilterTestCases:
137
147
  )
138
148
  This expects a field named `devices` on the model and a filter named `devices` on the filterset.
139
149
  """
150
+ if not any(test[0] == "id" for test in self.generic_filter_tests):
151
+ self.generic_filter_tests = (["id"], *self.generic_filter_tests)
152
+
140
153
  if getattr(self.queryset.model, "is_contact_associable_model", False):
141
- for generic_filter_test in (
142
- ["contacts", "associated_contacts__contact__name"],
143
- ["contacts", "associated_contacts__contact__id"],
144
- ["teams", "associated_contacts__team__name"],
145
- ["teams", "associated_contacts__team__id"],
146
- ):
147
- if generic_filter_test not in self.generic_filter_tests:
148
- self.generic_filter_tests = (*self.generic_filter_tests, generic_filter_test)
154
+ if not any(test[0] == "contacts" for test in self.generic_filter_tests):
155
+ self.generic_filter_tests = (
156
+ *self.generic_filter_tests,
157
+ ["contacts", "associated_contacts__contact__name"],
158
+ ["contacts", "associated_contacts__contact__id"],
159
+ )
160
+ if not any(test[0] == "teams" for test in self.generic_filter_tests):
161
+ self.generic_filter_tests = (
162
+ *self.generic_filter_tests,
163
+ ["teams", "associated_contacts__team__name"],
164
+ ["teams", "associated_contacts__team__id"],
165
+ )
149
166
 
150
167
  # Make sure we have at least 3 contacts and 3 teams in the database
151
168
  if Contact.objects.count() < 3:
@@ -173,9 +190,6 @@ class FilterTestCases:
173
190
  status=Status.objects.get_for_model(ContactAssociation).last(),
174
191
  )
175
192
 
176
- if not self.generic_filter_tests:
177
- self.skipTest("No generic_filter_tests defined?")
178
-
179
193
  for test in self.generic_filter_tests:
180
194
  filter_name = test[0]
181
195
  field_name = test[-1] # default to filter_name if a second list item was not supplied
@@ -234,10 +248,6 @@ class FilterTestCases:
234
248
  qs_result = self.queryset.filter(tags=tags[0]).filter(tags=tags[1]).distinct()
235
249
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
236
250
 
237
- def test_q_filter_exists(self):
238
- """Test the `q` filter exists on a filterset, does not validate the filter works as expected."""
239
- self.assertIn("q", self.filterset.declared_filters)
240
-
241
251
  def _assert_valid_filter_predicates(self, obj, field_name):
242
252
  self.assertTrue(
243
253
  hasattr(obj, field_name),
@@ -373,27 +383,27 @@ class FilterTestCases:
373
383
  filter_field, (ContentTypeFilter, ContentTypeMultipleChoiceFilter, ContentTypeChoiceFilter)
374
384
  )
375
385
 
386
+ # Test cases should just explicitly include `name` as a generic_filter_tests entry
387
+ @class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
376
388
  class NameOnlyFilterTestCase(FilterTestCase):
377
389
  """Add simple tests for filtering by name."""
378
390
 
379
- def test_name(self):
380
- """Verify that the filterset supports filtering by name."""
381
- params = {"name": list(self.queryset.values_list("name", flat=True)[:2])}
382
- filterset = self.filterset(params, self.queryset)
383
- self.assertTrue(filterset.is_valid())
384
- self.assertQuerysetEqualAndNotEmpty(
385
- filterset.qs.order_by("name"), self.queryset.filter(name__in=params["name"]).order_by("name")
386
- )
391
+ def test_filters_generic(self):
392
+ if not any(test[0] == "name" for test in self.generic_filter_tests):
393
+ self.generic_filter_tests = (["name"], *self.generic_filter_tests)
394
+ super().test_filters_generic()
387
395
 
388
- class NameSlugFilterTestCase(NameOnlyFilterTestCase):
396
+ # Test cases should just explicitly include `name` and `slug` as generic_filter_tests entries
397
+ @class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
398
+ class NameSlugFilterTestCase(FilterTestCase):
389
399
  """Add simple tests for filtering by name and by slug."""
390
400
 
391
- def test_slug(self):
392
- """Verify that the filterset supports filtering by slug."""
393
- params = {"slug": self.queryset.values_list("slug", flat=True)[:2]}
394
- filterset = self.filterset(params, self.queryset)
395
- self.assertTrue(filterset.is_valid())
396
- self.assertEqual(filterset.qs.count(), 2)
401
+ def test_filters_generic(self):
402
+ if not any(test[0] == "slug" for test in self.generic_filter_tests):
403
+ self.generic_filter_tests = (["slug"], *self.generic_filter_tests)
404
+ if not any(test[0] == "name" for test in self.generic_filter_tests):
405
+ self.generic_filter_tests = (["name"], *self.generic_filter_tests)
406
+ super().test_filters_generic()
397
407
 
398
408
  class TenancyFilterTestCaseMixin(views.TestCase):
399
409
  """Add test cases for tenant and tenant-group filters."""
@@ -1,5 +1,6 @@
1
1
  from django.test import tag, TestCase
2
2
 
3
+ from nautobot.core.api.constants import NON_FILTER_QUERY_PARAMS
3
4
  from nautobot.core.forms.fields import DynamicModelChoiceMixin
4
5
  from nautobot.core.utils.lookup import get_filterset_for_model
5
6
 
@@ -18,6 +19,7 @@ class FormTestCases:
18
19
  continue
19
20
  with self.subTest(f"Assert {self.form_class.__name__}.{field_name} query_params are valid."):
20
21
  query_params_fields = set(field_class.query_params.keys())
22
+ query_params_fields = query_params_fields - set(NON_FILTER_QUERY_PARAMS)
21
23
  if not query_params_fields:
22
24
  self.skipTest(f"{self.form_class.__name__}.{field_name} has no query_params")
23
25
  field_model = field_class.queryset.model
@@ -21,17 +21,19 @@ from nautobot.core.models.tree_queries import TreeModel
21
21
  from nautobot.core.templatetags import helpers
22
22
  from nautobot.core.testing import mixins, utils
23
23
  from nautobot.core.utils import lookup
24
+ from nautobot.dcim.models.device_components import ComponentModel
24
25
  from nautobot.extras import choices as extras_choices, models as extras_models, querysets as extras_querysets
25
26
  from nautobot.extras.forms import CustomFieldModelFormMixin, RelationshipModelFormMixin
26
27
  from nautobot.extras.models import CustomFieldModel, RelationshipModel
28
+ from nautobot.extras.models.jobs import JobResult
27
29
  from nautobot.extras.models.mixins import NotesMixin
28
30
  from nautobot.ipam.models import Prefix
29
31
  from nautobot.users import models as users_models
30
32
 
31
33
  __all__ = (
32
- "TestCase",
33
34
  "ModelTestCase",
34
35
  "ModelViewTestCase",
36
+ "TestCase",
35
37
  "ViewTestCases",
36
38
  )
37
39
 
@@ -171,10 +173,7 @@ class ViewTestCases:
171
173
  instance = self._get_queryset().first()
172
174
 
173
175
  # Add model-level permission
174
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["view"])
175
- obj_perm.save()
176
- obj_perm.users.add(self.user)
177
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
176
+ self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
178
177
 
179
178
  # Try GET with model-level permission
180
179
  response = self.client.get(instance.get_absolute_url())
@@ -323,10 +322,7 @@ class ViewTestCases:
323
322
  initial_count = self._get_queryset().count()
324
323
 
325
324
  # Assign unconstrained permission
326
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
327
- obj_perm.save()
328
- obj_perm.users.add(self.user)
329
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
325
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
330
326
 
331
327
  # Try GET with model-level permission
332
328
  self.assertHttpStatus(self.client.get(self._get_url("add")), 200)
@@ -433,32 +429,6 @@ class ViewTestCases:
433
429
  if isinstance(model_class, PrimaryModel):
434
430
  self.assertIsNotNone(fields.get("tags"))
435
431
 
436
- def test_slug_autocreation(self):
437
- """Test that slug is autocreated through ORM."""
438
- # This really should go on a models test page, but we don't have test structures for models.
439
- if getattr(self.model, "slug_source", None) is not None:
440
- obj = self.model.objects.get(**{self.slug_source: self.slug_test_object})
441
- expected_slug = self.slugify_function(getattr(obj, self.slug_source))
442
- self.assertEqual(obj.slug, expected_slug)
443
-
444
- def test_slug_not_modified(self):
445
- """Ensure save method does not modify slug that is passed in."""
446
- # This really should go on a models test page, but we don't have test structures for models.
447
- if getattr(self.model, "slug_source", None) is not None:
448
- new_slug_source_value = "kwyjibo"
449
-
450
- obj = self.model.objects.get(**{self.slug_source: self.slug_test_object})
451
- expected_slug = self.slugify_function(getattr(obj, self.slug_source))
452
- # Update slug source field str
453
- filter_ = self.slug_source + "__exact"
454
- self.model.objects.filter(**{filter_: self.slug_test_object}).update(
455
- **{self.slug_source: new_slug_source_value}
456
- )
457
-
458
- obj.refresh_from_db()
459
- self.assertEqual(getattr(obj, self.slug_source), new_slug_source_value)
460
- self.assertEqual(obj.slug, expected_slug)
461
-
462
432
  class EditObjectViewTestCase(ModelViewTestCase):
463
433
  """
464
434
  Edit a single existing instance.
@@ -491,10 +461,7 @@ class ViewTestCases:
491
461
  instance = self._get_queryset().first()
492
462
 
493
463
  # Assign model-level permission
494
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
495
- obj_perm.save()
496
- obj_perm.users.add(self.user)
497
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
464
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
498
465
 
499
466
  # Try GET with model-level permission
500
467
  self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), 200)
@@ -615,10 +582,7 @@ class ViewTestCases:
615
582
  instance_note_pk_list.append(note.pk)
616
583
 
617
584
  # Assign model-level permission
618
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
619
- obj_perm.save()
620
- obj_perm.users.add(self.user)
621
- obj_perm.object_types.add(assigned_object_type)
585
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
622
586
 
623
587
  # Try GET with model-level permission
624
588
  self.assertHttpStatus(self.client.get(self._get_url("delete", instance)), 200)
@@ -654,10 +618,7 @@ class ViewTestCases:
654
618
  instance = self.get_deletable_object()
655
619
 
656
620
  # Assign model-level permission
657
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
658
- obj_perm.save()
659
- obj_perm.users.add(self.user)
660
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
621
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
661
622
 
662
623
  # Try GET with model-level permission
663
624
  self.assertHttpStatus(self.client.get(self._get_url("delete", instance)), 200)
@@ -862,10 +823,7 @@ class ViewTestCases:
862
823
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
863
824
  def test_list_objects_with_permission(self):
864
825
  # Add model-level permission
865
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["view"])
866
- obj_perm.save()
867
- obj_perm.users.add(self.user)
868
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
826
+ self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
869
827
 
870
828
  # Try GET with model-level permission
871
829
  response = self.client.get(self._get_url("list"))
@@ -878,11 +836,17 @@ class ViewTestCases:
878
836
  # Check if breadcrumb is rendered correctly
879
837
  self.assertBodyContains(response, f'<a href="{list_url}">{title}</a>', html=True)
880
838
 
881
- # Check if import button is absent due to user permissions
882
- self.assertNotIn(
883
- reverse("extras:job_run_by_class_path", kwargs={"class_path": "nautobot.core.jobs.ImportObjects"}),
884
- response_body,
885
- )
839
+ with self.subTest("Assert import-objects URL is absent due to user permissions"):
840
+ self.assertNotIn(
841
+ reverse("extras:job_run_by_class_path", kwargs={"class_path": "nautobot.core.jobs.ImportObjects"}),
842
+ response_body,
843
+ )
844
+
845
+ if "example_app" in settings.PLUGINS:
846
+ with self.subTest("Assert example-app banner is present"):
847
+ self.assertIn(
848
+ f"<div>You are viewing a table of {self.model._meta.verbose_name_plural}</div>", response_body
849
+ )
886
850
 
887
851
  return response
888
852
 
@@ -989,13 +953,7 @@ class ViewTestCases:
989
953
  }
990
954
 
991
955
  # Assign non-constrained permission
992
- obj_perm = users_models.ObjectPermission(
993
- name="Test permission",
994
- actions=["add"],
995
- )
996
- obj_perm.save()
997
- obj_perm.users.add(self.user)
998
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
956
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
999
957
 
1000
958
  # Bulk create objects
1001
959
  response = self.client.post(**request)
@@ -1049,34 +1007,6 @@ class ViewTestCases:
1049
1007
  pass
1050
1008
  self.assertEqual(matching_count, self.bulk_create_count)
1051
1009
 
1052
- class BulkImportObjectsViewTestCase(ModelViewTestCase): # 3.0 TODO: remove this test mixin, no longer relevant.
1053
- """
1054
- Vestigial test case, to be removed in 3.0.
1055
-
1056
- This is vestigial since the introduction of the ImportObjects system Job to handle bulk-import of all
1057
- content-types via REST API serializers. The parsing of CSV data by the serializer is exercised by
1058
- APIViewTestCases.CreateObjectViewTestCase.test_recreate_object_csv(), and the basic operation of the Job is
1059
- exercised by nautobot.core.tests.test_jobs.
1060
- """
1061
-
1062
- csv_data = ()
1063
-
1064
- def _get_csv_data(self):
1065
- return "\n".join(self.csv_data)
1066
-
1067
- # Just in case Apps are extending any of these tests and calling super() in them.
1068
- def test_bulk_import_objects_without_permission(self):
1069
- pass
1070
-
1071
- def test_bulk_import_objects_with_permission(self):
1072
- pass
1073
-
1074
- def test_bulk_import_objects_with_permission_csv_file(self):
1075
- pass
1076
-
1077
- def test_bulk_import_objects_with_constrained_permission(self):
1078
- pass
1079
-
1080
1010
  class BulkEditObjectsViewTestCase(ModelViewTestCase):
1081
1011
  """
1082
1012
  Edit multiple instances.
@@ -1091,6 +1021,17 @@ class ViewTestCases:
1091
1021
  for instance in self._get_queryset().filter(pk__in=pk_list):
1092
1022
  self.assertInstanceEqual(instance, self.bulk_edit_data)
1093
1023
 
1024
+ def validate_redirect_to_job_result(self, response):
1025
+ # Get the last Bulk Edit Objects JobResult created
1026
+ job_result = JobResult.objects.filter(name="Bulk Edit Objects").first()
1027
+ # Assert redirect to Job Results
1028
+ self.assertRedirects(
1029
+ response,
1030
+ reverse("extras:jobresult", args=[job_result.pk]),
1031
+ status_code=302,
1032
+ target_status_code=200,
1033
+ )
1034
+
1094
1035
  def test_bulk_edit_objects_without_permission(self):
1095
1036
  pk_list = list(self._get_queryset().values_list("pk", flat=True)[:3])
1096
1037
  data = {
@@ -1114,14 +1055,10 @@ class ViewTestCases:
1114
1055
  data.update(utils.post_data(self.bulk_edit_data))
1115
1056
 
1116
1057
  # Assign model-level permission
1117
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1118
- obj_perm.save()
1119
- obj_perm.users.add(self.user)
1120
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1058
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1121
1059
 
1122
- # Try POST with model-level permission
1123
- self.assertHttpStatus(self.client.post(self._get_url("bulk_edit"), data), 302)
1124
- self.validate_object_data_after_bulk_edit(pk_list)
1060
+ response = self.client.post(self._get_url("bulk_edit"), data)
1061
+ self.validate_redirect_to_job_result(response)
1125
1062
 
1126
1063
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1127
1064
  def test_bulk_edit_form_contains_all_pks(self):
@@ -1132,10 +1069,7 @@ class ViewTestCases:
1132
1069
  "_all": "on",
1133
1070
  }
1134
1071
  # Assign model-level permission
1135
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1136
- obj_perm.save()
1137
- obj_perm.users.add(self.user)
1138
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1072
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1139
1073
 
1140
1074
  # Try POST with model-level permission
1141
1075
  response = self.client.post(self._get_url("bulk_edit"), selected_data)
@@ -1149,7 +1083,7 @@ class ViewTestCases:
1149
1083
  for pk in pk_list:
1150
1084
  self.assertNotIn(str(pk), response_body)
1151
1085
  self.assertInHTML(
1152
- '<input type="hidden" name="_all" value="True" class="form-control" required="required" placeholder="None" id="id__all">',
1086
+ '<input type="hidden" name="_all" value="True" class="form-control" placeholder="None" id="id__all">',
1153
1087
  response_body,
1154
1088
  )
1155
1089
 
@@ -1185,7 +1119,7 @@ class ViewTestCases:
1185
1119
  self.assertNotIn(str(third_pk), response_body)
1186
1120
  self.assertIn("Editing 2 ", response_body)
1187
1121
  self.assertInHTML(
1188
- '<input type="hidden" name="_all" value="True" class="form-control" required="required" placeholder="None" id="id__all">',
1122
+ '<input type="hidden" name="_all" value="True" class="form-control" placeholder="None" id="id__all">',
1189
1123
  response_body,
1190
1124
  )
1191
1125
 
@@ -1225,21 +1159,9 @@ class ViewTestCases:
1225
1159
 
1226
1160
  # Attempt to bulk edit permitted objects into a non-permitted state
1227
1161
  response = self.client.post(self._get_url("bulk_edit"), data)
1228
- # 200 because we're sent back to the edit form to try again; if the update were successful it'd be a 302
1229
- self.assertHttpStatus(response, 200)
1230
- # Assert that the objects are NOT updated
1231
- for instance in self._get_queryset().filter(pk__in=pk_list):
1232
- self.assertIn(field.value_from_object(instance), values)
1233
- self.assertNotEqual(field.value_from_object(instance), self.bulk_edit_data[attr_name])
1234
-
1235
- # Update permission constraints to permit all objects
1236
- obj_perm.constraints = {"pk__gt": 0}
1237
- obj_perm.save()
1238
-
1239
- # Bulk edit permitted objects and expect a redirect back to the list view
1240
- self.assertHttpStatus(self.client.post(self._get_url("bulk_edit"), data), 302)
1241
- # Assert that the objects were all updated correctly
1242
- self.validate_object_data_after_bulk_edit(pk_list)
1162
+ # NOTE: There is no way of testing constrained failure as bulk edit is a system Job;
1163
+ # and can only be tested by checking JobLogs.
1164
+ self.validate_redirect_to_job_result(response)
1243
1165
 
1244
1166
  class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
1245
1167
  """
@@ -1271,7 +1193,6 @@ class ViewTestCases:
1271
1193
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1272
1194
  def test_bulk_delete_objects_with_permission(self):
1273
1195
  pk_list = self.get_deletable_object_pks()
1274
- initial_count = self._get_queryset().count()
1275
1196
  data = {
1276
1197
  "pk": pk_list,
1277
1198
  "confirm": True,
@@ -1279,14 +1200,18 @@ class ViewTestCases:
1279
1200
  }
1280
1201
 
1281
1202
  # Assign unconstrained permission
1282
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1283
- obj_perm.save()
1284
- obj_perm.users.add(self.user)
1285
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1203
+ self.add_permissions(
1204
+ f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}", "extras.view_jobresult"
1205
+ )
1286
1206
 
1287
- # Try POST with model-level permission
1288
- self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302)
1289
- self.assertEqual(self._get_queryset().count(), initial_count - len(pk_list))
1207
+ response = self.client.post(self._get_url("bulk_delete"), data)
1208
+ job_result = JobResult.objects.filter(name="Bulk Delete Objects").first()
1209
+ self.assertRedirects(
1210
+ response,
1211
+ reverse("extras:jobresult", args=[job_result.pk]),
1212
+ status_code=302,
1213
+ target_status_code=200,
1214
+ )
1290
1215
 
1291
1216
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
1292
1217
  def test_bulk_delete_form_contains_all_objects(self):
@@ -1298,10 +1223,7 @@ class ViewTestCases:
1298
1223
  }
1299
1224
 
1300
1225
  # Assign unconstrained permission
1301
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["delete"])
1302
- obj_perm.save()
1303
- obj_perm.users.add(self.user)
1304
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1226
+ self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
1305
1227
 
1306
1228
  # Try POST with the selected data first. Emulating selecting all -> pressing Delete Selected button.
1307
1229
  response = self.client.post(self._get_url("bulk_delete"), selected_data)
@@ -1373,9 +1295,16 @@ class ViewTestCases:
1373
1295
  obj_perm.constraints = {"pk__isnull": False} # Match a non-existent pk (i.e., allow all)
1374
1296
  obj_perm.save()
1375
1297
 
1376
- # Bulk delete permitted objects
1377
- self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302)
1378
- self.assertEqual(self._get_queryset().count(), initial_count - len(pk_list))
1298
+ # User would be redirected to Job Result therefore user needs to have permission to view Job Result
1299
+ self.add_permissions("extras.view_jobresult")
1300
+ response = self.client.post(self._get_url("bulk_delete"), data)
1301
+ job_result = JobResult.objects.filter(name="Bulk Delete Objects").first()
1302
+ self.assertRedirects(
1303
+ response,
1304
+ reverse("extras:jobresult", args=[job_result.pk]),
1305
+ status_code=302,
1306
+ target_status_code=200,
1307
+ )
1379
1308
 
1380
1309
  class BulkRenameObjectsViewTestCase(ModelViewTestCase):
1381
1310
  """
@@ -1415,10 +1344,7 @@ class ViewTestCases:
1415
1344
  data.update(self.rename_data)
1416
1345
 
1417
1346
  # Assign model-level permission
1418
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1419
- obj_perm.save()
1420
- obj_perm.users.add(self.user)
1421
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1347
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1422
1348
 
1423
1349
  # Try POST with model-level permission
1424
1350
  self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
@@ -1528,14 +1454,13 @@ class ViewTestCases:
1528
1454
  maxDiff = None
1529
1455
  bulk_add_data = None
1530
1456
  """Used for bulk-add (distinct from bulk-create) view testing; self.bulk_create_data will be used if unset."""
1457
+ selected_objects: list[ComponentModel]
1458
+ selected_objects_parent_name: str
1531
1459
 
1532
1460
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1533
1461
  def test_bulk_add_component(self):
1534
1462
  """Test bulk-adding this component to devices/virtual-machines."""
1535
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["add"])
1536
- obj_perm.save()
1537
- obj_perm.users.add(self.user)
1538
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1463
+ self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
1539
1464
 
1540
1465
  initial_count = self._get_queryset().count()
1541
1466
 
@@ -1579,10 +1504,7 @@ class ViewTestCases:
1579
1504
 
1580
1505
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1581
1506
  def test_bulk_rename(self):
1582
- obj_perm = users_models.ObjectPermission(name="Test permission", actions=["change"])
1583
- obj_perm.save()
1584
- obj_perm.users.add(self.user)
1585
- obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
1507
+ self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
1586
1508
 
1587
1509
  objects = self.selected_objects
1588
1510
  pk_list = [obj.pk for obj in objects]
@@ -9,7 +9,6 @@ from example_app.models import ExampleModel
9
9
  class AppHomeTestCase(SeleniumTestCase):
10
10
  """Integration test the Example App homepage extensions."""
11
11
 
12
- fixtures = ["user-data.json"] # bob/bob
13
12
  layout = {
14
13
  "Organization": {
15
14
  "Locations": {"model": Location, "permission": "dcim.view_location"},
@@ -5,7 +5,6 @@ from nautobot.core.testing.integration import SeleniumTestCase
5
5
  class AppNavBarTestCase(SeleniumTestCase):
6
6
  """Integration test the navigation menu."""
7
7
 
8
- fixtures = ["user-data.json"]
9
8
  navbar = {
10
9
  "Circuits": {
11
10
  "Circuits": {
@@ -11,8 +11,6 @@ from nautobot.extras.models import CustomField, CustomFieldChoice, Status
11
11
  class ListViewFilterTestCase(SeleniumTestCase):
12
12
  """Integration test for the list view filter ui."""
13
13
 
14
- fixtures = ["user-data.json"]
15
-
16
14
  def setUp(self):
17
15
  super().setUp()
18
16
  self.login(self.user.username, self.password)
@@ -7,7 +7,6 @@ from nautobot.tenancy.models import Tenant
7
7
  class HomeTestCase(SeleniumTestCase):
8
8
  """Integration tests against the home page."""
9
9
 
10
- fixtures = ["user-data.json"] # bob/bob
11
10
  layout = {
12
11
  "Organization": {
13
12
  "Locations": {"model": Location, "permission": "dcim.view_location"},
@@ -4,7 +4,6 @@ from nautobot.core.testing.integration import SeleniumTestCase
4
4
  class NavBarTestCase(SeleniumTestCase):
5
5
  """Integration test the navigation menu."""
6
6
 
7
- fixtures = ["user-data.json"]
8
7
  navbar = {
9
8
  "Organization": {
10
9
  "Locations": {
@@ -14,7 +14,7 @@ class AuthenticationEnforcedTestCase(TestCase):
14
14
 
15
15
  def test_all_views_require_authentication(self):
16
16
  self.client.logout()
17
- url_patterns = get_url_patterns()
17
+ url_patterns = get_url_patterns(ignore_redirects=True)
18
18
 
19
19
  for url_pattern in url_patterns:
20
20
  with self.subTest(url_pattern=url_pattern):
@@ -31,6 +31,7 @@ class AuthenticationEnforcedTestCase(TestCase):
31
31
  "/health/",
32
32
  "/login/",
33
33
  "/media-failure/",
34
+ "/robots.txt",
34
35
  "/template.css",
35
36
  ]:
36
37
  self.assertHttpStatus(response, 200, msg=url)