nautobot 2.2.9__py3-none-any.whl → 2.3.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 (697) hide show
  1. nautobot/apps/forms.py +4 -0
  2. nautobot/apps/models.py +10 -1
  3. nautobot/circuits/__init__.py +0 -1
  4. nautobot/circuits/apps.py +1 -0
  5. nautobot/circuits/factory.py +15 -3
  6. nautobot/circuits/filters.py +13 -0
  7. nautobot/circuits/forms.py +13 -0
  8. nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
  9. nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
  10. nautobot/circuits/models.py +16 -3
  11. nautobot/circuits/tables.py +16 -2
  12. nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
  13. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
  14. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
  15. nautobot/circuits/tests/test_api.py +7 -5
  16. nautobot/circuits/tests/test_filters.py +12 -5
  17. nautobot/circuits/tests/test_models.py +33 -2
  18. nautobot/circuits/views.py +2 -3
  19. nautobot/cloud/__init__.py +0 -0
  20. nautobot/cloud/api/__init__.py +0 -0
  21. nautobot/cloud/api/serializers.py +54 -0
  22. nautobot/cloud/api/urls.py +16 -0
  23. nautobot/cloud/api/views.py +48 -0
  24. nautobot/cloud/apps.py +13 -0
  25. nautobot/cloud/factory.py +113 -0
  26. nautobot/cloud/filters.py +187 -0
  27. nautobot/cloud/forms.py +339 -0
  28. nautobot/cloud/homepage.py +43 -0
  29. nautobot/cloud/migrations/0001_initial.py +304 -0
  30. nautobot/cloud/migrations/__init__.py +0 -0
  31. nautobot/cloud/models.py +246 -0
  32. nautobot/cloud/navigation.py +85 -0
  33. nautobot/cloud/tables.py +157 -0
  34. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
  35. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
  36. nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
  37. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
  38. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -0
  39. nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
  40. nautobot/cloud/tests/__init__.py +0 -0
  41. nautobot/cloud/tests/test_api.py +248 -0
  42. nautobot/cloud/tests/test_filters.py +125 -0
  43. nautobot/cloud/tests/test_models.py +43 -0
  44. nautobot/cloud/tests/test_views.py +153 -0
  45. nautobot/cloud/urls.py +14 -0
  46. nautobot/cloud/views.py +181 -0
  47. nautobot/core/__init__.py +0 -3
  48. nautobot/core/api/metadata.py +1 -0
  49. nautobot/core/api/parsers.py +7 -1
  50. nautobot/core/api/urls.py +1 -0
  51. nautobot/core/api/utils.py +1 -0
  52. nautobot/core/api/views.py +4 -0
  53. nautobot/core/apps/__init__.py +6 -3
  54. nautobot/core/constants.py +8 -0
  55. nautobot/core/factory.py +32 -1
  56. nautobot/core/filters.py +95 -13
  57. nautobot/core/forms/fields.py +10 -4
  58. nautobot/core/forms/forms.py +11 -3
  59. nautobot/core/forms/widgets.py +18 -1
  60. nautobot/core/graphql/schema.py +26 -4
  61. nautobot/core/jobs/__init__.py +16 -2
  62. nautobot/core/jobs/cleanup.py +100 -0
  63. nautobot/core/jobs/groups.py +38 -0
  64. nautobot/core/management/commands/generate_test_data.py +116 -3
  65. nautobot/core/models/__init__.py +34 -9
  66. nautobot/core/models/generics.py +19 -3
  67. nautobot/core/models/name_color_content_types.py +7 -28
  68. nautobot/core/models/querysets.py +4 -3
  69. nautobot/core/models/tree_queries.py +1 -1
  70. nautobot/core/models/utils.py +21 -5
  71. nautobot/core/settings.py +2 -17
  72. nautobot/core/settings.yaml +34 -13
  73. nautobot/core/settings_funcs.py +103 -0
  74. nautobot/core/tables.py +130 -56
  75. nautobot/core/templates/admin/search_form.html +1 -1
  76. nautobot/core/templates/buttons/add.html +11 -3
  77. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  78. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/export.html +101 -53
  80. nautobot/core/templates/buttons/job_import.html +11 -3
  81. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  82. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  83. nautobot/core/templates/generic/object_changelog.html +0 -9
  84. nautobot/core/templates/generic/object_list.html +156 -17
  85. nautobot/core/templates/generic/object_retrieve.html +80 -16
  86. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  87. nautobot/core/templates/inc/javascript.html +2 -0
  88. nautobot/core/templates/inc/media.html +2 -2
  89. nautobot/core/templates/inc/nav_menu.html +1 -0
  90. nautobot/core/templates/inc/paginator.html +7 -7
  91. nautobot/core/templates/inc/search_panel.html +2 -2
  92. nautobot/core/templates/inc/table.html +2 -2
  93. nautobot/core/templates/nautobot_config.py.j2 +13 -8
  94. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  95. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  96. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  97. nautobot/core/templates/utilities/theme_preview.html +25 -8
  98. nautobot/core/templates/utilities/worker_status.html +152 -0
  99. nautobot/core/templatetags/buttons.py +335 -38
  100. nautobot/core/templatetags/form_helpers.py +1 -1
  101. nautobot/core/templatetags/helpers.py +181 -11
  102. nautobot/core/testing/api.py +5 -4
  103. nautobot/core/testing/filters.py +63 -14
  104. nautobot/core/testing/mixins.py +46 -0
  105. nautobot/core/testing/models.py +22 -0
  106. nautobot/core/testing/schema.py +4 -8
  107. nautobot/core/testing/views.py +31 -14
  108. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  109. nautobot/core/tests/integration/test_swagger.py +1 -1
  110. nautobot/core/tests/nautobot_config.py +0 -1
  111. nautobot/core/tests/runner.py +2 -2
  112. nautobot/core/tests/test_api.py +1 -0
  113. nautobot/core/tests/test_authentication.py +7 -2
  114. nautobot/core/tests/test_filters.py +11 -9
  115. nautobot/core/tests/test_forms.py +9 -0
  116. nautobot/core/tests/test_graphql.py +27 -16
  117. nautobot/core/tests/test_jobs.py +122 -0
  118. nautobot/core/tests/test_tables.py +3 -1
  119. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  120. nautobot/core/tests/test_utils.py +31 -20
  121. nautobot/core/tests/test_views.py +6 -6
  122. nautobot/core/urls.py +8 -3
  123. nautobot/core/utils/deprecation.py +29 -0
  124. nautobot/core/utils/filtering.py +12 -9
  125. nautobot/core/utils/lookup.py +37 -2
  126. nautobot/core/utils/requests.py +4 -1
  127. nautobot/core/views/__init__.py +137 -24
  128. nautobot/core/views/generic.py +119 -67
  129. nautobot/core/views/mixins.py +105 -36
  130. nautobot/core/views/paginator.py +9 -3
  131. nautobot/core/views/renderers.py +121 -56
  132. nautobot/core/views/utils.py +81 -1
  133. nautobot/dcim/__init__.py +0 -1
  134. nautobot/dcim/api/serializers.py +180 -44
  135. nautobot/dcim/api/urls.py +7 -3
  136. nautobot/dcim/api/views.py +53 -7
  137. nautobot/dcim/apps.py +3 -0
  138. nautobot/dcim/choices.py +25 -0
  139. nautobot/dcim/constants.py +7 -0
  140. nautobot/dcim/factory.py +252 -18
  141. nautobot/dcim/filters/__init__.py +373 -193
  142. nautobot/dcim/filters/mixins.py +274 -1
  143. nautobot/dcim/forms.py +834 -121
  144. nautobot/dcim/graphql/types.py +2 -2
  145. nautobot/dcim/homepage.py +1 -1
  146. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  147. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  148. nautobot/dcim/migrations/0061_module_models.py +862 -0
  149. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  150. nautobot/dcim/models/__init__.py +8 -0
  151. nautobot/dcim/models/cables.py +15 -0
  152. nautobot/dcim/models/device_component_templates.py +207 -53
  153. nautobot/dcim/models/device_components.py +275 -99
  154. nautobot/dcim/models/devices.py +468 -13
  155. nautobot/dcim/models/racks.py +0 -1
  156. nautobot/dcim/navigation.py +47 -0
  157. nautobot/dcim/signals.py +3 -3
  158. nautobot/dcim/tables/__init__.py +35 -23
  159. nautobot/dcim/tables/devices.py +229 -43
  160. nautobot/dcim/tables/devicetypes.py +65 -9
  161. nautobot/dcim/tables/racks.py +5 -1
  162. nautobot/dcim/tables/template_code.py +46 -26
  163. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  164. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  165. nautobot/dcim/templates/dcim/device/base.html +14 -6
  166. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  167. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  168. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  169. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  170. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  171. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  172. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  173. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  174. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  175. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  176. nautobot/dcim/templates/dcim/device.html +45 -1
  177. nautobot/dcim/templates/dcim/device_component.html +13 -5
  178. nautobot/dcim/templates/dcim/device_list.html +2 -1
  179. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  180. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  181. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  182. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  183. nautobot/dcim/templates/dcim/interface.html +17 -2
  184. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  185. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  186. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  187. nautobot/dcim/templates/dcim/module/base.html +97 -0
  188. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  189. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  190. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  191. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  192. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  193. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  194. nautobot/dcim/templates/dcim/module_list.html +20 -0
  195. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  196. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  197. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  198. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  199. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  200. nautobot/dcim/templates/dcim/module_update.html +71 -0
  201. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  202. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  203. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  204. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  205. nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
  206. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  207. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  208. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  209. nautobot/dcim/tests/test_api.py +693 -208
  210. nautobot/dcim/tests/test_filters.py +843 -217
  211. nautobot/dcim/tests/test_models.py +1072 -8
  212. nautobot/dcim/tests/test_views.py +1510 -341
  213. nautobot/dcim/urls.py +17 -2
  214. nautobot/dcim/utils.py +2 -3
  215. nautobot/dcim/views.py +1106 -116
  216. nautobot/extras/__init__.py +0 -1
  217. nautobot/extras/api/serializers.py +115 -3
  218. nautobot/extras/api/urls.py +12 -0
  219. nautobot/extras/api/views.py +66 -0
  220. nautobot/extras/apps.py +2 -2
  221. nautobot/extras/choices.py +43 -0
  222. nautobot/extras/context_managers.py +13 -8
  223. nautobot/extras/datasources/git.py +2 -0
  224. nautobot/extras/factory.py +460 -9
  225. nautobot/extras/filters/__init__.py +174 -3
  226. nautobot/extras/filters/mixins.py +46 -43
  227. nautobot/extras/forms/base.py +24 -5
  228. nautobot/extras/forms/forms.py +227 -8
  229. nautobot/extras/forms/mixins.py +93 -0
  230. nautobot/extras/graphql/types.py +23 -10
  231. nautobot/extras/homepage.py +14 -1
  232. nautobot/extras/management/__init__.py +1 -0
  233. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  234. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  235. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  236. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  237. nautobot/extras/migrations/0111_metadata.py +162 -0
  238. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  239. nautobot/extras/migrations/0113_saved_views.py +77 -0
  240. nautobot/extras/models/__init__.py +15 -1
  241. nautobot/extras/models/change_logging.py +3 -3
  242. nautobot/extras/models/contacts.py +4 -0
  243. nautobot/extras/models/customfields.py +18 -3
  244. nautobot/extras/models/groups.py +389 -225
  245. nautobot/extras/models/jobs.py +6 -3
  246. nautobot/extras/models/metadata.py +441 -0
  247. nautobot/extras/models/mixins.py +72 -62
  248. nautobot/extras/models/models.py +118 -9
  249. nautobot/extras/models/relationships.py +9 -2
  250. nautobot/extras/models/tags.py +13 -2
  251. nautobot/extras/navigation.py +57 -0
  252. nautobot/extras/plugins/__init__.py +3 -1
  253. nautobot/extras/querysets.py +30 -66
  254. nautobot/extras/signals.py +95 -100
  255. nautobot/extras/tables.py +165 -12
  256. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  257. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  258. nautobot/extras/templates/extras/job.html +1 -1
  259. nautobot/extras/templates/extras/jobresult.html +61 -74
  260. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  261. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  262. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  263. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  264. nautobot/extras/templates/extras/plugins_list.html +1 -3
  265. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  266. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  267. nautobot/extras/tests/integration/test_customfields.py +1 -0
  268. nautobot/extras/tests/test_api.py +509 -23
  269. nautobot/extras/tests/test_changelog.py +20 -9
  270. nautobot/extras/tests/test_context_managers.py +22 -15
  271. nautobot/extras/tests/test_datasources.py +13 -1
  272. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  273. nautobot/extras/tests/test_filters.py +211 -12
  274. nautobot/extras/tests/test_jobs.py +6 -6
  275. nautobot/extras/tests/test_models.py +501 -4
  276. nautobot/extras/tests/test_relationships.py +1 -0
  277. nautobot/extras/tests/test_views.py +565 -8
  278. nautobot/extras/tests/test_webhooks.py +1 -1
  279. nautobot/extras/urls.py +5 -0
  280. nautobot/extras/utils.py +51 -11
  281. nautobot/extras/views.py +542 -76
  282. nautobot/ipam/__init__.py +0 -1
  283. nautobot/ipam/apps.py +1 -0
  284. nautobot/ipam/factory.py +17 -19
  285. nautobot/ipam/filters.py +13 -0
  286. nautobot/ipam/forms.py +8 -4
  287. nautobot/ipam/graphql/types.py +2 -2
  288. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  289. nautobot/ipam/models.py +11 -8
  290. nautobot/ipam/querysets.py +1 -1
  291. nautobot/ipam/signals.py +4 -2
  292. nautobot/ipam/tables.py +5 -0
  293. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  294. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  295. nautobot/ipam/templates/ipam/prefix.html +1 -0
  296. nautobot/ipam/tests/test_api.py +37 -18
  297. nautobot/ipam/tests/test_filters.py +26 -2
  298. nautobot/ipam/tests/test_models.py +6 -0
  299. nautobot/ipam/tests/test_querysets.py +1 -1
  300. nautobot/ipam/tests/test_views.py +3 -2
  301. nautobot/ipam/urls.py +2 -2
  302. nautobot/ipam/views.py +18 -26
  303. nautobot/project-static/css/base.css +20 -0
  304. nautobot/project-static/css/dark.css +11 -0
  305. nautobot/project-static/docs/404.html +892 -88
  306. nautobot/project-static/docs/apps/index.html +892 -88
  307. nautobot/project-static/docs/apps/nautobot-apps.html +892 -88
  308. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  309. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  310. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  311. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +919 -120
  312. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +904 -101
  313. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1618 -903
  314. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +935 -144
  315. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +977 -188
  316. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +901 -99
  317. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +897 -93
  318. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +991 -193
  319. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +974 -131
  320. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1078 -272
  321. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1242 -334
  322. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1727 -875
  323. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1164 -381
  324. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2088 -1374
  325. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2246 -1422
  326. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +912 -111
  327. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +963 -163
  328. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1010 -223
  329. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1913 -1277
  330. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1846 -1102
  331. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +904 -101
  332. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2331 -1699
  333. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1802 -1024
  334. nautobot/project-static/docs/development/apps/api/configuration-view.html +892 -88
  335. nautobot/project-static/docs/development/apps/api/database-backend-config.html +892 -88
  336. nautobot/project-static/docs/development/apps/api/models/django-admin.html +892 -88
  337. nautobot/project-static/docs/development/apps/api/models/global-search.html +892 -88
  338. nautobot/project-static/docs/development/apps/api/models/graphql.html +892 -88
  339. nautobot/project-static/docs/development/apps/api/models/index.html +942 -90
  340. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +892 -88
  341. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +892 -88
  342. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +892 -88
  343. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +892 -88
  344. nautobot/project-static/docs/development/apps/api/platform-features/index.html +892 -88
  345. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +892 -88
  346. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +892 -88
  347. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +892 -88
  348. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +892 -88
  349. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +892 -88
  350. nautobot/project-static/docs/development/apps/api/prometheus.html +892 -88
  351. nautobot/project-static/docs/development/apps/api/setup.html +892 -88
  352. nautobot/project-static/docs/development/apps/api/testing.html +892 -88
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +892 -88
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +892 -88
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +892 -88
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +892 -88
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +892 -88
  358. nautobot/project-static/docs/development/apps/api/views/base-template.html +892 -88
  359. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +892 -88
  360. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +892 -88
  361. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +892 -88
  362. nautobot/project-static/docs/development/apps/api/views/index.html +892 -88
  363. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +892 -88
  364. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +892 -88
  365. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +892 -88
  366. nautobot/project-static/docs/development/apps/api/views/notes.html +892 -88
  367. nautobot/project-static/docs/development/apps/api/views/rest-api.html +892 -88
  368. nautobot/project-static/docs/development/apps/api/views/urls.html +892 -88
  369. nautobot/project-static/docs/development/apps/index.html +892 -88
  370. nautobot/project-static/docs/development/apps/migration/code-updates.html +892 -88
  371. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +892 -88
  372. nautobot/project-static/docs/development/apps/migration/from-v1.html +892 -88
  373. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +892 -88
  374. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +892 -88
  375. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +892 -88
  376. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +892 -88
  377. nautobot/project-static/docs/development/apps/porting-from-netbox.html +892 -88
  378. nautobot/project-static/docs/development/core/application-registry.html +892 -88
  379. nautobot/project-static/docs/development/core/best-practices.html +893 -88
  380. nautobot/project-static/docs/development/core/bootstrap-ui.html +892 -88
  381. nautobot/project-static/docs/development/core/caching.html +892 -88
  382. nautobot/project-static/docs/development/core/controllers.html +892 -88
  383. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +892 -88
  384. nautobot/project-static/docs/development/core/generic-views.html +892 -88
  385. nautobot/project-static/docs/development/core/getting-started.html +892 -88
  386. nautobot/project-static/docs/development/core/homepage.html +892 -88
  387. nautobot/project-static/docs/development/core/index.html +892 -88
  388. nautobot/project-static/docs/development/core/model-checklist.html +901 -89
  389. nautobot/project-static/docs/development/core/model-features.html +892 -88
  390. nautobot/project-static/docs/development/core/natural-keys.html +892 -88
  391. nautobot/project-static/docs/development/core/navigation-menu.html +892 -88
  392. nautobot/project-static/docs/development/core/release-checklist.html +895 -91
  393. nautobot/project-static/docs/development/core/role-internals.html +892 -88
  394. nautobot/project-static/docs/development/core/settings.html +892 -88
  395. nautobot/project-static/docs/development/core/style-guide.html +893 -89
  396. nautobot/project-static/docs/development/core/templates.html +904 -89
  397. nautobot/project-static/docs/development/core/testing.html +892 -88
  398. nautobot/project-static/docs/development/core/user-preferences.html +892 -88
  399. nautobot/project-static/docs/development/index.html +892 -88
  400. nautobot/project-static/docs/development/jobs/index.html +893 -89
  401. nautobot/project-static/docs/development/jobs/migration/from-v1.html +892 -88
  402. nautobot/project-static/docs/index.html +892 -88
  403. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  404. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  405. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  406. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  407. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  408. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  409. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  410. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  411. nautobot/project-static/docs/models/dcim/module.html +15 -0
  412. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  413. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  414. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  415. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  416. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  417. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  418. nautobot/project-static/docs/models/extras/role.html +15 -0
  419. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  420. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  421. nautobot/project-static/docs/models/extras/status.html +15 -0
  422. nautobot/project-static/docs/objects.inv +0 -0
  423. nautobot/project-static/docs/overview/application_stack.html +900 -89
  424. nautobot/project-static/docs/overview/design_philosophy.html +892 -88
  425. nautobot/project-static/docs/release-notes/index.html +1129 -92
  426. nautobot/project-static/docs/release-notes/version-1.0.html +892 -88
  427. nautobot/project-static/docs/release-notes/version-1.1.html +892 -88
  428. nautobot/project-static/docs/release-notes/version-1.2.html +892 -88
  429. nautobot/project-static/docs/release-notes/version-1.3.html +892 -88
  430. nautobot/project-static/docs/release-notes/version-1.4.html +892 -88
  431. nautobot/project-static/docs/release-notes/version-1.5.html +893 -89
  432. nautobot/project-static/docs/release-notes/version-1.6.html +893 -89
  433. nautobot/project-static/docs/release-notes/version-2.0.html +892 -88
  434. nautobot/project-static/docs/release-notes/version-2.1.html +892 -88
  435. nautobot/project-static/docs/release-notes/version-2.2.html +895 -91
  436. nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
  437. nautobot/project-static/docs/requirements.txt +5 -5
  438. nautobot/project-static/docs/search/search_index.json +1 -1
  439. nautobot/project-static/docs/sitemap.xml +331 -256
  440. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  441. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +892 -88
  442. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +892 -88
  443. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +892 -88
  444. nautobot/project-static/docs/user-guide/administration/configuration/index.html +892 -88
  445. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +992 -174
  446. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +892 -88
  447. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +892 -88
  448. nautobot/project-static/docs/user-guide/administration/guides/caching.html +892 -88
  449. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +896 -88
  450. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +892 -88
  451. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +892 -88
  452. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +892 -88
  453. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +892 -88
  454. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +892 -88
  455. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +892 -88
  456. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +892 -88
  457. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +892 -88
  458. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +944 -153
  459. nautobot/project-static/docs/user-guide/administration/installation/index.html +901 -93
  460. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +934 -122
  461. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +954 -157
  462. nautobot/project-static/docs/user-guide/administration/installation/services.html +913 -112
  463. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +908 -99
  464. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +892 -88
  465. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +892 -88
  466. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +892 -88
  467. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +892 -88
  468. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +893 -89
  469. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +892 -88
  470. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +892 -88
  471. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +892 -88
  472. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +892 -88
  473. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +892 -88
  474. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +892 -88
  475. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +892 -88
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +892 -88
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +892 -88
  478. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +892 -88
  479. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +892 -88
  480. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +893 -89
  481. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +892 -88
  482. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +896 -88
  483. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +895 -91
  484. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  485. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  486. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  487. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  488. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  491. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +906 -102
  492. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +923 -105
  493. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +923 -105
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +918 -100
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +923 -105
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +906 -102
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +906 -102
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +913 -105
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +920 -116
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +921 -117
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +918 -114
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +906 -102
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +914 -105
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +926 -108
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +936 -118
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +928 -106
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +906 -102
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +937 -119
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +928 -110
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +918 -114
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +921 -117
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +923 -115
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +916 -112
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +906 -102
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +940 -83
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +924 -106
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +906 -102
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +943 -86
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +921 -103
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +929 -125
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +918 -114
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +906 -102
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +922 -104
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +924 -106
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +906 -102
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +906 -102
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +906 -102
  532. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +936 -88
  533. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +892 -88
  534. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +897 -89
  535. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +897 -89
  536. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +892 -88
  537. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +892 -88
  538. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +892 -88
  539. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +892 -88
  540. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +892 -88
  541. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +892 -88
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +892 -88
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +892 -88
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +892 -88
  545. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +892 -88
  546. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +901 -96
  547. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +892 -88
  548. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +892 -88
  549. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +892 -88
  550. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +892 -88
  551. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +892 -88
  552. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +897 -89
  553. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +892 -88
  554. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +892 -88
  555. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +892 -88
  556. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +892 -88
  557. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +892 -88
  558. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +892 -88
  559. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +892 -88
  560. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +892 -88
  561. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +892 -88
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +892 -88
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +892 -88
  564. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +892 -88
  565. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +892 -88
  566. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  567. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  568. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  569. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  570. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  571. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  572. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +892 -88
  592. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +892 -88
  593. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +892 -88
  594. nautobot/project-static/docs/user-guide/index.html +892 -88
  595. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +892 -88
  596. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +892 -88
  597. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +892 -88
  598. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +892 -88
  599. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1258 -785
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +895 -91
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +892 -88
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +892 -88
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +892 -88
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +892 -88
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +892 -88
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +892 -88
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +892 -88
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +892 -88
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +892 -88
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +896 -88
  611. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +892 -88
  612. nautobot/project-static/docs/user-guide/platform-functionality/note.html +895 -91
  613. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
  614. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +895 -91
  615. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +892 -88
  616. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +892 -88
  617. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +892 -88
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +892 -88
  619. nautobot/project-static/docs/user-guide/platform-functionality/role.html +895 -91
  620. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  621. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +895 -91
  622. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  623. nautobot/project-static/docs/user-guide/platform-functionality/status.html +892 -88
  624. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +892 -88
  625. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +950 -121
  626. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +892 -88
  627. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +892 -88
  628. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +892 -88
  629. nautobot/project-static/js/forms.js +71 -0
  630. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  631. nautobot/project-static/js/tableconfig.js +6 -1
  632. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  633. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  634. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  635. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  636. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  637. nautobot/tenancy/__init__.py +0 -1
  638. nautobot/tenancy/apps.py +1 -0
  639. nautobot/tenancy/factory.py +3 -2
  640. nautobot/tenancy/filters/__init__.py +1 -0
  641. nautobot/tenancy/forms.py +1 -1
  642. nautobot/tenancy/templates/tenancy/tenant.html +24 -20
  643. nautobot/tenancy/views.py +11 -10
  644. nautobot/users/__init__.py +0 -1
  645. nautobot/users/api/serializers.py +1 -1
  646. nautobot/users/api/views.py +4 -2
  647. nautobot/users/apps.py +3 -2
  648. nautobot/users/factory.py +3 -3
  649. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  650. nautobot/users/models.py +12 -0
  651. nautobot/users/tests/test_filters.py +6 -3
  652. nautobot/users/urls.py +8 -0
  653. nautobot/virtualization/__init__.py +0 -1
  654. nautobot/virtualization/apps.py +1 -0
  655. nautobot/virtualization/filters.py +6 -1
  656. nautobot/virtualization/forms.py +11 -3
  657. nautobot/virtualization/graphql/types.py +2 -2
  658. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  659. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  660. nautobot/virtualization/models.py +0 -2
  661. nautobot/virtualization/tables.py +10 -3
  662. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  663. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  664. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  665. nautobot/virtualization/tests/test_api.py +9 -4
  666. nautobot/virtualization/tests/test_filters.py +22 -0
  667. nautobot/virtualization/tests/test_models.py +7 -3
  668. nautobot/virtualization/tests/test_views.py +19 -3
  669. nautobot/virtualization/urls.py +2 -2
  670. nautobot/virtualization/views.py +10 -32
  671. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/METADATA +20 -18
  672. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/RECORD +677 -557
  673. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  674. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  675. nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
  676. nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
  677. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
  678. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
  679. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
  680. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
  681. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
  682. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
  683. nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
  684. nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
  685. nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
  686. nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
  687. nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
  688. nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
  689. nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
  690. nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
  691. nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
  692. nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
  693. /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
  694. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
  695. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
  696. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
  697. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -5,6 +5,7 @@ import re
5
5
  from django.conf import settings
6
6
  from django.contrib import messages
7
7
  from django.contrib.auth.mixins import LoginRequiredMixin
8
+ from django.contrib.auth.models import AnonymousUser
8
9
  from django.contrib.contenttypes.models import ContentType
9
10
  from django.core.exceptions import (
10
11
  FieldDoesNotExist,
@@ -12,10 +13,11 @@ from django.core.exceptions import (
12
13
  ValidationError,
13
14
  )
14
15
  from django.db import IntegrityError, transaction
15
- from django.db.models import ManyToManyField, ProtectedError
16
+ from django.db.models import ManyToManyField, ProtectedError, Q
16
17
  from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
17
- from django.http import HttpResponse, JsonResponse
18
+ from django.http import HttpResponse
18
19
  from django.shortcuts import get_object_or_404, redirect, render
20
+ from django.urls import reverse
19
21
  from django.utils.encoding import iri_to_uri
20
22
  from django.utils.html import format_html
21
23
  from django.utils.http import url_has_allowed_host_and_scheme
@@ -38,7 +40,6 @@ from nautobot.core.forms import (
38
40
  from nautobot.core.forms.forms import DynamicFilterFormSet
39
41
  from nautobot.core.templatetags.helpers import bettertitle, validated_viewname
40
42
  from nautobot.core.utils.config import get_settings_or_config
41
- from nautobot.core.utils.lookup import get_created_and_last_updated_usernames_for_model
42
43
  from nautobot.core.utils.permissions import get_permission_for_model
43
44
  from nautobot.core.utils.requests import (
44
45
  convert_querydict_to_factory_formset_acceptable_querydict,
@@ -49,14 +50,15 @@ from nautobot.core.views.mixins import GetReturnURLMixin, ObjectPermissionRequir
49
50
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
50
51
  from nautobot.core.views.utils import (
51
52
  check_filter_for_display,
53
+ common_detail_view_context,
52
54
  get_csv_form_fields_from_serializer_class,
53
55
  handle_protectederror,
54
56
  import_csv_helper,
55
57
  prepare_cloned_fields,
58
+ view_changes_not_saved,
56
59
  )
57
60
  from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
58
- from nautobot.extras.models import ContactAssociation, ExportTemplate
59
- from nautobot.extras.tables import AssociatedContactsTable
61
+ from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
60
62
  from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, remove_prefix_from_cf_key
61
63
 
62
64
 
@@ -111,53 +113,17 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
111
113
  Generic GET handler for accessing an object.
112
114
  """
113
115
  instance = get_object_or_404(self.queryset, **kwargs)
114
- # Get the ObjectChange records to populate the advanced tab information
115
- created_by, last_updated_by = get_created_and_last_updated_usernames_for_model(instance)
116
-
117
- # TODO: this feels inelegant - should the tabs lookup be a dedicated endpoint rather than piggybacking
118
- # on the object-retrieve endpoint?
119
- # TODO: similar functionality probably needed in NautobotUIViewSet as well, not currently present
120
- if request.GET.get("viewconfig", None) == "true":
121
- # TODO: we shouldn't be importing a private-named function from another module. Should it be renamed?
122
- from nautobot.extras.templatetags.plugins import _get_registered_content
123
-
124
- temp_fake_context = {
125
- "object": instance,
126
- "request": request,
127
- "settings": {},
128
- "csrf_token": "",
129
- "perms": {},
130
- }
116
+ content_type = ContentType.objects.get_for_model(self.queryset.model)
117
+ context = {
118
+ "object": instance,
119
+ "content_type": content_type,
120
+ "verbose_name": self.queryset.model._meta.verbose_name,
121
+ "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
122
+ **common_detail_view_context(request, instance),
123
+ **self.get_extra_context(request, instance),
124
+ }
131
125
 
132
- plugin_tabs = _get_registered_content(instance, "detail_tabs", temp_fake_context, return_html=False)
133
- resp = {"tabs": plugin_tabs}
134
- return JsonResponse(resp)
135
- else:
136
- content_type = ContentType.objects.get_for_model(self.queryset.model)
137
- context = {
138
- "object": instance,
139
- "content_type": content_type,
140
- "verbose_name": self.queryset.model._meta.verbose_name,
141
- "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
142
- "created_by": created_by,
143
- "last_updated_by": last_updated_by,
144
- **self.get_extra_context(request, instance),
145
- }
146
- if instance.is_contact_associable_model:
147
- paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
148
- associations = (
149
- ContactAssociation.objects.filter(
150
- associated_object_id=instance.id,
151
- associated_object_type=content_type,
152
- )
153
- .restrict(request.user, "view")
154
- .order_by("role__name")
155
- )
156
- associations_table = AssociatedContactsTable(associations, orderable=False)
157
- RequestConfig(request, paginate).configure(associations_table)
158
- associations_table.columns.show("pk")
159
- context["associated_contacts_table"] = associations_table
160
- return render(request, self.get_template_name(), context)
126
+ return render(request, self.get_template_name(), context)
161
127
 
162
128
 
163
129
  class ObjectListView(ObjectPermissionRequiredMixin, View):
@@ -184,12 +150,19 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
184
150
  "page", # used by django-tables2.RequestConfig
185
151
  "per_page", # used by get_paginate_count
186
152
  "sort", # table sorting
153
+ "saved_view", # saved_view indicator pk or composite keys
154
+ "table_changes_pending", # indicator for if there is any table changes not applied to the saved view
155
+ "all_filters_removed", # indicator for if all filters have been removed from the saved view
156
+ "clear_view", # indicator for if the clear view button is clicked or not
187
157
  )
188
158
 
189
159
  def get_filter_params(self, request):
190
160
  """Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
191
- filter_params = request.GET.copy()
192
- return get_filterable_params_from_filter_params(filter_params, self.non_filter_params, self.filterset())
161
+ params = request.GET.copy()
162
+ filter_params = get_filterable_params_from_filter_params(params, self.non_filter_params, self.filterset())
163
+ if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
164
+ return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
165
+ return filter_params
193
166
 
194
167
  def get_required_permission(self):
195
168
  return get_permission_for_model(self.queryset.model, "view")
@@ -225,10 +198,41 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
225
198
  model = self.queryset.model
226
199
  content_type = ContentType.objects.get_for_model(model)
227
200
 
201
+ filter_params = request.GET
202
+ user = request.user
228
203
  display_filter_params = []
229
204
  dynamic_filter_form = None
230
205
  filter_form = None
231
206
  hide_hierarchy_ui = False
207
+ clear_view = request.GET.get("clear_view", False)
208
+
209
+ # If the user clicks on the clear view button, we do not check for global or user defaults
210
+ if not clear_view and not request.GET.get("saved_view"):
211
+ # Check if there is a default for this view for this specific user
212
+ app_label, model_name = model._meta.label.split(".")
213
+ view_name = f"{app_label}:{model_name.lower()}_list"
214
+
215
+ if not isinstance(user, AnonymousUser):
216
+ try:
217
+ user_default_saved_view_pk = UserSavedViewAssociation.objects.get(
218
+ user=user, view_name=view_name
219
+ ).saved_view.pk
220
+ # Saved view should either belong to the user or be public
221
+ SavedView.objects.get(
222
+ Q(pk=user_default_saved_view_pk),
223
+ Q(owner=user) | Q(is_shared=True),
224
+ )
225
+ sv_url = reverse("extras:savedview", kwargs={"pk": user_default_saved_view_pk})
226
+ return redirect(sv_url)
227
+ except ObjectDoesNotExist:
228
+ pass
229
+
230
+ # Check if there is a global default for this view
231
+ try:
232
+ global_saved_view = SavedView.objects.get(view=view_name, is_global_default=True)
233
+ return redirect(reverse("extras:savedview", kwargs={"pk": global_saved_view.pk}))
234
+ except ObjectDoesNotExist:
235
+ pass
232
236
 
233
237
  if self.filterset:
234
238
  filter_params = self.get_filter_params(request)
@@ -254,7 +258,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
254
258
 
255
259
  if request.GET:
256
260
  factory_formset_params = convert_querydict_to_factory_formset_acceptable_querydict(
257
- request.GET, filterset
261
+ filter_params, filterset
258
262
  )
259
263
  dynamic_filter_form = DynamicFilterFormSet(filterset=filterset, data=factory_formset_params)
260
264
  else:
@@ -296,18 +300,50 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
296
300
 
297
301
  table = None
298
302
  table_config_form = None
303
+ current_saved_view = None
304
+ current_saved_view_pk = self.request.GET.get("saved_view", None)
305
+ list_url = validated_viewname(model, "list")
306
+ # We are not using .restrict(request.user, "view") here
307
+ # User should be able to see any saved view that he has the list view access to.
308
+ if user.has_perms(["extras.view_savedview"]):
309
+ saved_views = SavedView.objects.filter(view=list_url).order_by("name").only("pk", "name")
310
+ else:
311
+ shared_saved_views = (
312
+ SavedView.objects.filter(view=list_url, is_shared=True).order_by("name").only("pk", "name")
313
+ )
314
+ user_owned_saved_views = (
315
+ SavedView.objects.filter(view=list_url, owner=user).order_by("name").only("pk", "name")
316
+ )
317
+ saved_views = shared_saved_views | user_owned_saved_views
299
318
  if self.table:
300
319
  # Construct the objects table
301
- if self.request.GET.getlist("sort"):
320
+ if current_saved_view_pk:
321
+ try:
322
+ # We are not using .restrict(request.user, "view") here
323
+ # User should be able to see any saved view that he has the list view access to.
324
+ current_saved_view = SavedView.objects.get(view=list_url, pk=current_saved_view_pk)
325
+ except ObjectDoesNotExist:
326
+ messages.error(request, f"Saved view {current_saved_view_pk} not found")
327
+ if self.request.GET.getlist("sort") or (
328
+ current_saved_view is not None and current_saved_view.config.get("sort_order")
329
+ ):
302
330
  hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
303
- table = self.table(self.queryset, user=request.user, hide_hierarchy_ui=hide_hierarchy_ui)
331
+ table_changes_pending = self.request.GET.get("table_changes_pending", False)
332
+
333
+ table = self.table(
334
+ self.queryset,
335
+ table_changes_pending=table_changes_pending,
336
+ saved_view=current_saved_view,
337
+ user=request.user,
338
+ hide_hierarchy_ui=hide_hierarchy_ui,
339
+ )
304
340
  if "pk" in table.base_columns and (permissions["change"] or permissions["delete"]):
305
341
  table.columns.show("pk")
306
342
 
307
343
  # Apply the request context
308
344
  paginate = {
309
345
  "paginator_class": EnhancedPaginator,
310
- "per_page": get_paginate_count(request),
346
+ "per_page": get_paginate_count(request, current_saved_view),
311
347
  }
312
348
  RequestConfig(request, paginate).configure(table)
313
349
  table_config_form = TableConfigForm(table=table)
@@ -320,10 +356,13 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
320
356
 
321
357
  # For the search form field, use a custom placeholder.
322
358
  q_placeholder = "Search " + bettertitle(model._meta.verbose_name_plural)
323
- search_form = SearchForm(data=request.GET, q_placeholder=q_placeholder)
359
+ search_form = SearchForm(data=filter_params, q_placeholder=q_placeholder)
324
360
 
325
361
  valid_actions = self.validate_action_buttons(request)
326
362
 
363
+ # Query SavedViews for dropdown button
364
+ new_changes_not_applied = view_changes_not_saved(request, self, current_saved_view)
365
+
327
366
  context = {
328
367
  "content_type": content_type,
329
368
  "table": table,
@@ -334,8 +373,12 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
334
373
  "filter_form": filter_form,
335
374
  "dynamic_filter_form": dynamic_filter_form,
336
375
  "search_form": search_form,
337
- "list_url": validated_viewname(model, "list"),
376
+ "list_url": list_url,
338
377
  "title": bettertitle(model._meta.verbose_name_plural),
378
+ "new_changes_not_applied": new_changes_not_applied,
379
+ "current_saved_view": current_saved_view,
380
+ "saved_views": saved_views,
381
+ "model": model,
339
382
  }
340
383
 
341
384
  # `extra_context()` would require `request` access, however `request` parameter cannot simply be
@@ -439,7 +482,12 @@ class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
439
482
  def post(self, request, *args, **kwargs):
440
483
  logger = logging.getLogger(__name__ + ".ObjectEditView")
441
484
  obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
442
- form = self.model_form(data=request.POST, files=request.FILES, instance=obj)
485
+ form = self.model_form(
486
+ data=request.POST,
487
+ files=request.FILES,
488
+ initial=normalize_querydict(request.GET, form_class=self.model_form),
489
+ instance=obj,
490
+ )
443
491
  restrict_form_fields(form, request.user)
444
492
 
445
493
  if form.is_valid():
@@ -538,6 +586,7 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
538
586
 
539
587
  if form.is_valid():
540
588
  logger.debug("Form validation was successful")
589
+ msg = f"Deleted {self.queryset.model._meta.verbose_name} {obj}"
541
590
 
542
591
  try:
543
592
  obj.delete()
@@ -546,7 +595,6 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
546
595
  handle_protectederror([obj], request, e)
547
596
  return redirect(obj.get_absolute_url())
548
597
 
549
- msg = f"Deleted {self.queryset.model._meta.verbose_name} {obj}"
550
598
  logger.info(msg)
551
599
  messages.success(request, msg)
552
600
 
@@ -752,7 +800,9 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
752
800
 
753
801
  related_obj_pks = []
754
802
  for i, rel_obj_data in enumerate(data.get(field_name, [])):
755
- f = related_object_form(obj, rel_obj_data)
803
+ # add parent object key to related object data
804
+ rel_obj_data[obj._meta.verbose_name.replace(" ", "_")] = str(obj.pk)
805
+ f = related_object_form(rel_obj_data)
756
806
 
757
807
  for subfield_name, field in f.fields.items():
758
808
  if subfield_name not in rel_obj_data and hasattr(field, "initial"):
@@ -1210,7 +1260,7 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
1210
1260
  Delete objects in bulk.
1211
1261
 
1212
1262
  queryset: Custom queryset to use when retrieving objects (e.g. to select related objects)
1213
- filter: FilterSet to apply when deleting by QuerySet
1263
+ filterset: FilterSet to apply when deleting by QuerySet
1214
1264
  table: The table used to display devices being deleted
1215
1265
  form: The form class used to delete objects in bulk
1216
1266
  template_name: The name of the template
@@ -1336,7 +1386,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1336
1386
  return get_permission_for_model(self.queryset.model, "add")
1337
1387
 
1338
1388
  def get(self, request):
1339
- form = self.form(initial=request.GET)
1389
+ form = self.form(initial=normalize_querydict(request.GET, form_class=self.form))
1340
1390
  model_form = self.model_form(request.GET)
1341
1391
 
1342
1392
  return render(
@@ -1352,8 +1402,8 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1352
1402
 
1353
1403
  def post(self, request):
1354
1404
  logger = logging.getLogger(__name__ + ".ComponentCreateView")
1355
- form = self.form(request.POST, initial=request.GET)
1356
- model_form = self.model_form(request.POST)
1405
+ form = self.form(request.POST, initial=normalize_querydict(request.GET, form_class=self.form))
1406
+ model_form = self.model_form(request.POST, initial=normalize_querydict(request.GET, form_class=self.model_form))
1357
1407
 
1358
1408
  if form.is_valid():
1359
1409
  new_components = []
@@ -1368,7 +1418,9 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
1368
1418
  data["label"] = label
1369
1419
  if hasattr(form, "get_iterative_data"):
1370
1420
  data.update(form.get_iterative_data(i))
1371
- component_form = self.model_form(data)
1421
+ component_form = self.model_form(
1422
+ data, initial=normalize_querydict(request.GET, form_class=self.model_form)
1423
+ )
1372
1424
 
1373
1425
  if component_form.is_valid():
1374
1426
  new_components.append(component_form)
@@ -2,6 +2,7 @@ import logging
2
2
 
3
3
  from django.contrib import messages
4
4
  from django.contrib.auth.mixins import AccessMixin
5
+ from django.contrib.auth.models import AnonymousUser
5
6
  from django.contrib.contenttypes.models import ContentType
6
7
  from django.core.exceptions import (
7
8
  FieldDoesNotExist,
@@ -10,7 +11,7 @@ from django.core.exceptions import (
10
11
  ValidationError,
11
12
  )
12
13
  from django.db import transaction
13
- from django.db.models import ManyToManyField, ProtectedError
14
+ from django.db.models import ManyToManyField, ProtectedError, Q
14
15
  from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
15
16
  from django.http import HttpResponse
16
17
  from django.shortcuts import get_object_or_404, redirect
@@ -37,7 +38,7 @@ from nautobot.core.forms import (
37
38
  restrict_form_fields,
38
39
  )
39
40
  from nautobot.core.utils import lookup, permissions
40
- from nautobot.core.utils.requests import get_filterable_params_from_filter_params
41
+ from nautobot.core.utils.requests import get_filterable_params_from_filter_params, normalize_querydict
41
42
  from nautobot.core.views.renderers import NautobotHTMLRenderer
42
43
  from nautobot.core.views.utils import (
43
44
  get_csv_form_fields_from_serializer_class,
@@ -47,9 +48,9 @@ from nautobot.core.views.utils import (
47
48
  )
48
49
  from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
49
50
  from nautobot.extras.forms import NoteForm
50
- from nautobot.extras.models import ExportTemplate
51
+ from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
51
52
  from nautobot.extras.tables import NoteTable, ObjectChangeTable
52
- from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, remove_prefix_from_cf_key
53
+ from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, get_base_template, remove_prefix_from_cf_key
53
54
 
54
55
  PERMISSIONS_ACTION_MAP = {
55
56
  "list": "view",
@@ -59,6 +60,7 @@ PERMISSIONS_ACTION_MAP = {
59
60
  "update": "change",
60
61
  "bulk_create": "add", # 3.0 TODO: remove, replaced by system Job
61
62
  "bulk_destroy": "delete",
63
+ "bulk_rename": "change",
62
64
  "bulk_update": "change",
63
65
  "changelog": "view",
64
66
  "notes": "view",
@@ -459,8 +461,11 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
459
461
 
460
462
  def get_filter_params(self, request):
461
463
  """Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
462
- filter_params = request.GET.copy()
463
- return get_filterable_params_from_filter_params(filter_params, self.non_filter_params, self.filterset_class())
464
+ params = request.GET.copy()
465
+ filter_params = get_filterable_params_from_filter_params(params, self.non_filter_params, self.filterset_class())
466
+ if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
467
+ return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
468
+ return filter_params
464
469
 
465
470
  def get_queryset(self):
466
471
  """
@@ -586,6 +591,9 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
586
591
  queryset = self.get_queryset()
587
592
  return queryset.all()
588
593
 
594
+ def extra_post_save_action(self, obj, form):
595
+ """Extra actions after a form is saved"""
596
+
589
597
 
590
598
  class ObjectDetailViewMixin(NautobotViewSetMixin, mixins.RetrieveModelMixin):
591
599
  """
@@ -596,11 +604,7 @@ class ObjectDetailViewMixin(NautobotViewSetMixin, mixins.RetrieveModelMixin):
596
604
  """
597
605
  Retrieve a model instance.
598
606
  """
599
- instance = self.get_object()
600
- serializer = self.get_serializer(instance)
601
-
602
- context = serializer.data
603
- context["use_new_ui"] = True
607
+ context = {"use_new_ui": True}
604
608
  return Response(context)
605
609
 
606
610
 
@@ -618,6 +622,10 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
618
622
  "page", # used by django-tables2.RequestConfig
619
623
  "per_page", # used by get_paginate_count
620
624
  "sort", # table sorting
625
+ "saved_view", # saved_view indicator pk or composite keys
626
+ "table_changes_pending", # indicator for if there is any table changes not applied to the saved view
627
+ "all_filters_removed", # indicator for if all filters have been removed from the saved view
628
+ "clear_view", # indicator for if the clear view button is clicked or not
621
629
  )
622
630
 
623
631
  def filter_queryset(self, queryset):
@@ -684,13 +692,43 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
684
692
  List the model instances.
685
693
  """
686
694
  context = {"use_new_ui": True}
695
+ queryset = self.get_queryset()
696
+ clear_view = request.GET.get("clear_view", False)
687
697
  if "export" in request.GET: # 3.0 TODO: remove, irrelevant after #4746
688
- queryset = self.get_queryset()
689
698
  model = queryset.model
690
699
  content_type = ContentType.objects.get_for_model(model)
691
700
  response = self.check_for_export(request, model, content_type)
692
701
  if response is not None:
693
702
  return response
703
+
704
+ # If the user clicks on the clear view button, we do not check for global or user defaults
705
+ if not clear_view and not request.GET.get("saved_view"):
706
+ # Check if there is a default for this view for this specific user
707
+ app_label, model_name = queryset.model._meta.label.split(".")
708
+ view_name = f"{app_label}:{model_name.lower()}_list"
709
+ user = request.user
710
+ if not isinstance(user, AnonymousUser):
711
+ try:
712
+ user_default_saved_view_pk = UserSavedViewAssociation.objects.get(
713
+ user=user, view_name=view_name
714
+ ).saved_view.pk
715
+ # Saved view should either belong to the user or be public
716
+ SavedView.objects.get(
717
+ Q(pk=user_default_saved_view_pk),
718
+ Q(owner=user) | Q(is_shared=True),
719
+ )
720
+ sv_url = reverse("extras:savedview", kwargs={"pk": user_default_saved_view_pk})
721
+ return redirect(sv_url)
722
+ except ObjectDoesNotExist:
723
+ pass
724
+
725
+ # Check if there is a global default for this view
726
+ try:
727
+ global_saved_view = SavedView.objects.get(view=view_name, is_global_default=True)
728
+ return redirect(reverse("extras:savedview", kwargs={"pk": global_saved_view.pk}))
729
+ except ObjectDoesNotExist:
730
+ pass
731
+
694
732
  return Response(context)
695
733
 
696
734
 
@@ -707,8 +745,8 @@ class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
707
745
  queryset = self.get_queryset()
708
746
  try:
709
747
  with transaction.atomic():
710
- obj.delete()
711
748
  msg = f"Deleted {queryset.model._meta.verbose_name} {obj}"
749
+ obj.delete()
712
750
  self.logger.info(msg)
713
751
  messages.success(request, msg)
714
752
  self.success_url = self.get_return_url(request, obj)
@@ -734,7 +772,7 @@ class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
734
772
  """
735
773
  self.obj = self.get_object()
736
774
  form_class = self.get_form_class()
737
- form = form_class(request.POST)
775
+ form = form_class(request.POST, initial=normalize_querydict(request.GET, form_class=form_class))
738
776
  if form.is_valid():
739
777
  return self.form_valid(form)
740
778
  else:
@@ -746,6 +784,18 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
746
784
  UI mixin to create or update a model instance.
747
785
  """
748
786
 
787
+ def extra_message_context(self, obj):
788
+ """
789
+ Context variables for this extra message.
790
+ """
791
+ return {}
792
+
793
+ def extra_message(self, **kwargs):
794
+ """
795
+ Append extra message at the end of create or update success message.
796
+ """
797
+ return ""
798
+
749
799
  def _process_create_or_update_form(self, form):
750
800
  """
751
801
  Helper method to create or update an object after the form is validated successfully.
@@ -765,9 +815,14 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
765
815
  msg = f'{"Created" if object_created else "Modified"} {queryset.model._meta.verbose_name}'
766
816
  self.logger.info(f"{msg} {obj} (PK: {obj.pk})")
767
817
  try:
768
- msg = format_html('{} <a href="{}">{}</a>', msg, obj.get_absolute_url(), obj)
818
+ msg = format_html(
819
+ '{} <a href="{}">{}</a>' + self.extra_message(**self.extra_message_context(obj)),
820
+ msg,
821
+ obj.get_absolute_url(),
822
+ obj,
823
+ )
769
824
  except AttributeError:
770
- msg = format_html("{} {}", msg, obj)
825
+ msg = format_html("{} {}" + self.extra_message(**self.extra_message_context(obj)), msg, obj)
771
826
  messages.success(request, msg)
772
827
  if "_addanother" in request.POST:
773
828
  # If the object has clone_fields, pre-populate a new instance of the form
@@ -800,7 +855,12 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
800
855
  """
801
856
  self.obj = self.get_object()
802
857
  form_class = self.get_form_class()
803
- form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
858
+ form = form_class(
859
+ data=request.POST,
860
+ files=request.FILES,
861
+ initial=normalize_querydict(request.GET, form_class=form_class),
862
+ instance=self.obj,
863
+ )
804
864
  restrict_form_fields(form, request.user)
805
865
  if form.is_valid():
806
866
  return self.form_valid(form)
@@ -825,7 +885,12 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
825
885
  """
826
886
  self.obj = self.get_object()
827
887
  form_class = self.get_form_class()
828
- form = form_class(data=request.POST, files=request.FILES, instance=self.obj)
888
+ form = form_class(
889
+ data=request.POST,
890
+ files=request.FILES,
891
+ initial=normalize_querydict(request.GET, form_class=form_class),
892
+ instance=self.obj,
893
+ )
829
894
  restrict_form_fields(form, request.user)
830
895
  if form.is_valid():
831
896
  return self.form_valid(form)
@@ -878,21 +943,18 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin):
878
943
  model = queryset.model
879
944
  # Are we deleting *all* objects in the queryset or just a selected subset?
880
945
  if request.POST.get("_all"):
881
- filter_params = self.get_filter_params(request)
882
- if not filter_params:
883
- self.pk_list = list(model.objects.only("pk").all().values_list("pk", flat=True))
884
- elif self.filterset_class is None:
885
- raise NotImplementedError("filterset_class must be defined to use _all")
886
- else:
946
+ if self.filterset_class is not None:
887
947
  self.pk_list = list(
888
- self.filterset_class(filter_params, model.objects.only("pk")).qs.values_list("pk", flat=True)
948
+ self.filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True)
889
949
  )
950
+ else:
951
+ self.pk_list = list(model.objects.all().values_list("pk", flat=True))
890
952
  else:
891
953
  self.pk_list = list(request.POST.getlist("pk"))
892
954
  form_class = self.get_form_class(**kwargs)
893
955
  data = {}
894
956
  if "_confirm" in request.POST:
895
- form = form_class(request.POST)
957
+ form = form_class(request.POST, initial=normalize_querydict(request.GET, form_class=form_class))
896
958
  if form.is_valid():
897
959
  return self.form_valid(form)
898
960
  else:
@@ -953,7 +1015,11 @@ class ObjectBulkCreateViewMixin(NautobotViewSetMixin): # 3.0 TODO: remove, unus
953
1015
 
954
1016
  def perform_bulk_create(self, request):
955
1017
  form_class = self.get_form_class()
956
- form = form_class(request.POST, request.FILES)
1018
+ form = form_class(
1019
+ data=request.POST,
1020
+ files=request.FILES,
1021
+ initial=normalize_querydict(request.GET, form_class=form_class),
1022
+ )
957
1023
  if form.is_valid():
958
1024
  return self.form_valid(form)
959
1025
  else:
@@ -1031,6 +1097,8 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
1031
1097
  if hasattr(form, "save_note") and callable(form.save_note):
1032
1098
  form.save_note(instance=obj, user=request.user)
1033
1099
 
1100
+ self.extra_post_save_action(obj, form)
1101
+
1034
1102
  # Enforce object-level permissions
1035
1103
  if queryset.filter(pk__in=[obj.pk for obj in updated_objects]).count() != len(updated_objects):
1036
1104
  raise ObjectDoesNotExist
@@ -1059,15 +1127,12 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
1059
1127
 
1060
1128
  # If we are editing *all* objects in the queryset, replace the PK list with all matched objects.
1061
1129
  if request.POST.get("_all"):
1062
- filter_params = self.get_filter_params(request)
1063
- if not filter_params:
1064
- self.pk_list = list(model.objects.only("pk").all().values_list("pk", flat=True))
1065
- elif self.filterset_class is None:
1066
- raise NotImplementedError("filterset_class must be defined to use _all")
1067
- else:
1130
+ if self.filterset_class is not None:
1068
1131
  self.pk_list = list(
1069
- self.filterset_class(filter_params, model.objects.only("pk")).qs.values_list("pk", flat=True)
1132
+ self.filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True)
1070
1133
  )
1134
+ else:
1135
+ self.pk_list = list(model.objects.all().values_list("pk", flat=True))
1071
1136
  else:
1072
1137
  self.pk_list = list(request.POST.getlist("pk"))
1073
1138
  data = {}
@@ -1101,8 +1166,10 @@ class ObjectChangeLogViewMixin(NautobotViewSetMixin):
1101
1166
 
1102
1167
  @drf_action(detail=True)
1103
1168
  def changelog(self, request, *args, **kwargs):
1169
+ model = self.get_queryset().model
1104
1170
  data = {
1105
- "base_template": self.base_template,
1171
+ "base_template": get_base_template(self.base_template, model),
1172
+ "active_tab": "changelog",
1106
1173
  }
1107
1174
  return Response(data)
1108
1175
 
@@ -1116,7 +1183,9 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
1116
1183
 
1117
1184
  @drf_action(detail=True)
1118
1185
  def notes(self, request, *args, **kwargs):
1186
+ model = self.get_queryset().model
1119
1187
  data = {
1120
- "base_template": self.base_template,
1188
+ "base_template": get_base_template(self.base_template, model),
1189
+ "active_tab": "notes",
1121
1190
  }
1122
1191
  return Response(data)