nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (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 +111 -0
  26. nautobot/cloud/filters.py +184 -0
  27. nautobot/cloud/forms.py +333 -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 +247 -0
  32. nautobot/cloud/navigation.py +85 -0
  33. nautobot/cloud/tables.py +173 -0
  34. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
  35. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
  36. nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
  37. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
  38. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -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 +113 -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 +96 -28
  57. nautobot/core/forms/fields.py +10 -4
  58. nautobot/core/forms/forms.py +1 -1
  59. nautobot/core/forms/widgets.py +18 -1
  60. nautobot/core/graphql/generators.py +2 -2
  61. nautobot/core/graphql/schema.py +34 -4
  62. nautobot/core/jobs/__init__.py +17 -6
  63. nautobot/core/jobs/cleanup.py +100 -0
  64. nautobot/core/jobs/groups.py +38 -0
  65. nautobot/core/management/commands/generate_test_data.py +116 -3
  66. nautobot/core/models/__init__.py +34 -9
  67. nautobot/core/models/generics.py +19 -3
  68. nautobot/core/models/name_color_content_types.py +7 -28
  69. nautobot/core/models/querysets.py +4 -3
  70. nautobot/core/models/tree_queries.py +1 -1
  71. nautobot/core/models/utils.py +21 -5
  72. nautobot/core/settings.py +4 -30
  73. nautobot/core/settings.yaml +34 -27
  74. nautobot/core/settings_funcs.py +103 -0
  75. nautobot/core/tables.py +127 -56
  76. nautobot/core/templates/admin/search_form.html +1 -1
  77. nautobot/core/templates/buttons/add.html +11 -3
  78. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  80. nautobot/core/templates/buttons/export.html +101 -53
  81. nautobot/core/templates/buttons/job_import.html +11 -3
  82. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  83. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  84. nautobot/core/templates/generic/object_changelog.html +0 -9
  85. nautobot/core/templates/generic/object_list.html +156 -17
  86. nautobot/core/templates/generic/object_retrieve.html +80 -16
  87. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  88. nautobot/core/templates/inc/javascript.html +2 -0
  89. nautobot/core/templates/inc/media.html +2 -2
  90. nautobot/core/templates/inc/nav_menu.html +1 -0
  91. nautobot/core/templates/inc/paginator.html +7 -7
  92. nautobot/core/templates/inc/search_panel.html +2 -2
  93. nautobot/core/templates/inc/table.html +2 -2
  94. nautobot/core/templates/nautobot_config.py.j2 +13 -23
  95. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  96. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  97. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  98. nautobot/core/templates/utilities/theme_preview.html +25 -8
  99. nautobot/core/templates/utilities/worker_status.html +152 -0
  100. nautobot/core/templatetags/buttons.py +335 -38
  101. nautobot/core/templatetags/form_helpers.py +1 -1
  102. nautobot/core/templatetags/helpers.py +181 -11
  103. nautobot/core/testing/api.py +5 -4
  104. nautobot/core/testing/filters.py +51 -13
  105. nautobot/core/testing/mixins.py +46 -0
  106. nautobot/core/testing/models.py +22 -0
  107. nautobot/core/testing/schema.py +4 -8
  108. nautobot/core/testing/views.py +31 -14
  109. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  110. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  111. nautobot/core/tests/integration/test_swagger.py +1 -1
  112. nautobot/core/tests/nautobot_config.py +0 -1
  113. nautobot/core/tests/runner.py +2 -2
  114. nautobot/core/tests/test_api.py +1 -0
  115. nautobot/core/tests/test_authentication.py +7 -2
  116. nautobot/core/tests/test_filters.py +11 -9
  117. nautobot/core/tests/test_forms.py +9 -0
  118. nautobot/core/tests/test_graphql.py +27 -16
  119. nautobot/core/tests/test_jobs.py +123 -74
  120. nautobot/core/tests/test_tables.py +3 -1
  121. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  122. nautobot/core/tests/test_utils.py +31 -20
  123. nautobot/core/tests/test_views.py +6 -6
  124. nautobot/core/urls.py +8 -3
  125. nautobot/core/utils/deprecation.py +29 -0
  126. nautobot/core/utils/filtering.py +12 -9
  127. nautobot/core/utils/lookup.py +37 -2
  128. nautobot/core/utils/requests.py +4 -1
  129. nautobot/core/views/__init__.py +137 -24
  130. nautobot/core/views/generic.py +118 -66
  131. nautobot/core/views/mixins.py +104 -35
  132. nautobot/core/views/paginator.py +9 -3
  133. nautobot/core/views/renderers.py +121 -56
  134. nautobot/core/views/utils.py +79 -1
  135. nautobot/dcim/__init__.py +0 -1
  136. nautobot/dcim/api/serializers.py +180 -44
  137. nautobot/dcim/api/urls.py +7 -3
  138. nautobot/dcim/api/views.py +53 -7
  139. nautobot/dcim/apps.py +3 -0
  140. nautobot/dcim/choices.py +25 -0
  141. nautobot/dcim/constants.py +7 -0
  142. nautobot/dcim/factory.py +249 -18
  143. nautobot/dcim/filters/__init__.py +369 -193
  144. nautobot/dcim/filters/mixins.py +274 -1
  145. nautobot/dcim/forms.py +817 -109
  146. nautobot/dcim/graphql/types.py +2 -2
  147. nautobot/dcim/homepage.py +1 -1
  148. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  149. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  150. nautobot/dcim/migrations/0061_module_models.py +861 -0
  151. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  152. nautobot/dcim/models/__init__.py +8 -0
  153. nautobot/dcim/models/cables.py +15 -0
  154. nautobot/dcim/models/device_component_templates.py +207 -53
  155. nautobot/dcim/models/device_components.py +275 -106
  156. nautobot/dcim/models/devices.py +466 -13
  157. nautobot/dcim/navigation.py +47 -0
  158. nautobot/dcim/signals.py +3 -3
  159. nautobot/dcim/tables/__init__.py +35 -23
  160. nautobot/dcim/tables/devices.py +231 -59
  161. nautobot/dcim/tables/devicetypes.py +65 -9
  162. nautobot/dcim/tables/racks.py +5 -1
  163. nautobot/dcim/tables/template_code.py +46 -26
  164. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  165. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  166. nautobot/dcim/templates/dcim/device/base.html +15 -7
  167. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  168. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  169. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  170. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  171. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  172. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  173. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  174. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  175. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  176. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  177. nautobot/dcim/templates/dcim/device.html +45 -1
  178. nautobot/dcim/templates/dcim/device_component.html +13 -5
  179. nautobot/dcim/templates/dcim/device_list.html +2 -1
  180. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
  181. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  182. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  183. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  184. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  185. nautobot/dcim/templates/dcim/interface.html +17 -2
  186. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  187. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  188. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  189. nautobot/dcim/templates/dcim/module/base.html +97 -0
  190. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  191. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  192. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  193. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  194. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  195. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  196. nautobot/dcim/templates/dcim/module_list.html +20 -0
  197. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  198. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  199. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  200. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  201. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  202. nautobot/dcim/templates/dcim/module_update.html +71 -0
  203. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  204. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  205. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  206. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  207. nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
  208. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  209. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  210. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  211. nautobot/dcim/tests/test_api.py +691 -208
  212. nautobot/dcim/tests/test_filters.py +836 -217
  213. nautobot/dcim/tests/test_models.py +1072 -39
  214. nautobot/dcim/tests/test_views.py +1488 -358
  215. nautobot/dcim/urls.py +17 -2
  216. nautobot/dcim/utils.py +2 -3
  217. nautobot/dcim/views.py +1107 -120
  218. nautobot/extras/__init__.py +0 -1
  219. nautobot/extras/api/serializers.py +115 -3
  220. nautobot/extras/api/urls.py +12 -0
  221. nautobot/extras/api/views.py +125 -7
  222. nautobot/extras/apps.py +2 -2
  223. nautobot/extras/choices.py +43 -0
  224. nautobot/extras/context_managers.py +13 -8
  225. nautobot/extras/datasources/git.py +2 -0
  226. nautobot/extras/factory.py +422 -9
  227. nautobot/extras/filters/__init__.py +174 -3
  228. nautobot/extras/filters/mixins.py +46 -43
  229. nautobot/extras/forms/base.py +17 -4
  230. nautobot/extras/forms/forms.py +227 -8
  231. nautobot/extras/forms/mixins.py +93 -0
  232. nautobot/extras/graphql/types.py +23 -10
  233. nautobot/extras/homepage.py +16 -13
  234. nautobot/extras/jobs.py +2 -2
  235. nautobot/extras/management/__init__.py +1 -0
  236. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  237. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  238. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  239. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  240. nautobot/extras/migrations/0111_metadata.py +162 -0
  241. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  242. nautobot/extras/migrations/0113_saved_views.py +77 -0
  243. nautobot/extras/models/__init__.py +15 -1
  244. nautobot/extras/models/change_logging.py +3 -3
  245. nautobot/extras/models/contacts.py +4 -0
  246. nautobot/extras/models/customfields.py +18 -3
  247. nautobot/extras/models/groups.py +389 -225
  248. nautobot/extras/models/jobs.py +4 -84
  249. nautobot/extras/models/metadata.py +441 -0
  250. nautobot/extras/models/mixins.py +72 -62
  251. nautobot/extras/models/models.py +116 -9
  252. nautobot/extras/models/relationships.py +9 -2
  253. nautobot/extras/models/tags.py +13 -2
  254. nautobot/extras/navigation.py +57 -0
  255. nautobot/extras/plugins/__init__.py +3 -1
  256. nautobot/extras/querysets.py +30 -66
  257. nautobot/extras/signals.py +96 -114
  258. nautobot/extras/tables.py +171 -47
  259. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  260. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  261. nautobot/extras/templates/extras/job.html +1 -1
  262. nautobot/extras/templates/extras/job_detail.html +0 -11
  263. nautobot/extras/templates/extras/jobresult.html +61 -74
  264. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  265. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  266. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  267. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  268. nautobot/extras/templates/extras/plugins_list.html +1 -3
  269. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  270. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  271. nautobot/extras/tests/integration/test_customfields.py +1 -0
  272. nautobot/extras/tests/test_api.py +501 -22
  273. nautobot/extras/tests/test_changelog.py +20 -9
  274. nautobot/extras/tests/test_context_managers.py +22 -15
  275. nautobot/extras/tests/test_datasources.py +13 -1
  276. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  277. nautobot/extras/tests/test_filters.py +211 -12
  278. nautobot/extras/tests/test_jobs.py +4 -4
  279. nautobot/extras/tests/test_models.py +499 -4
  280. nautobot/extras/tests/test_relationships.py +1 -0
  281. nautobot/extras/tests/test_views.py +565 -28
  282. nautobot/extras/tests/test_webhooks.py +1 -1
  283. nautobot/extras/urls.py +5 -0
  284. nautobot/extras/utils.py +56 -45
  285. nautobot/extras/views.py +585 -96
  286. nautobot/ipam/__init__.py +0 -1
  287. nautobot/ipam/apps.py +1 -0
  288. nautobot/ipam/factory.py +17 -19
  289. nautobot/ipam/filters.py +14 -1
  290. nautobot/ipam/forms.py +9 -5
  291. nautobot/ipam/graphql/types.py +2 -2
  292. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  293. nautobot/ipam/models.py +23 -9
  294. nautobot/ipam/querysets.py +1 -1
  295. nautobot/ipam/signals.py +4 -2
  296. nautobot/ipam/tables.py +1 -0
  297. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  298. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  299. nautobot/ipam/templates/ipam/prefix.html +1 -0
  300. nautobot/ipam/tests/test_api.py +37 -18
  301. nautobot/ipam/tests/test_filters.py +26 -2
  302. nautobot/ipam/tests/test_models.py +8 -3
  303. nautobot/ipam/tests/test_querysets.py +1 -1
  304. nautobot/ipam/tests/test_views.py +3 -2
  305. nautobot/ipam/urls.py +2 -2
  306. nautobot/ipam/views.py +25 -28
  307. nautobot/project-static/css/base.css +20 -1
  308. nautobot/project-static/css/dark.css +11 -0
  309. nautobot/project-static/docs/404.html +884 -80
  310. nautobot/project-static/docs/apps/index.html +884 -80
  311. nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
  312. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  313. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
  314. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
  315. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
  316. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
  317. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
  318. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
  319. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
  320. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
  321. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
  322. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
  323. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
  324. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
  325. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
  326. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
  327. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
  328. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
  329. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
  330. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
  331. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
  332. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
  333. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
  334. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
  335. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
  336. nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
  337. nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
  338. nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
  339. nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
  340. nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
  341. nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
  342. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
  343. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
  344. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
  345. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
  346. nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
  347. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
  348. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
  349. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
  350. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
  351. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
  352. nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
  353. nautobot/project-static/docs/development/apps/api/setup.html +884 -80
  354. nautobot/project-static/docs/development/apps/api/testing.html +884 -80
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
  358. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
  359. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
  360. nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
  361. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
  362. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
  363. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
  364. nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
  365. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
  366. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
  367. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
  368. nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
  369. nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
  370. nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
  371. nautobot/project-static/docs/development/apps/index.html +884 -80
  372. nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
  373. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
  374. nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
  375. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
  376. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
  377. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
  378. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
  380. nautobot/project-static/docs/development/core/application-registry.html +884 -80
  381. nautobot/project-static/docs/development/core/best-practices.html +885 -80
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
  383. nautobot/project-static/docs/development/core/caching.html +884 -80
  384. nautobot/project-static/docs/development/core/controllers.html +884 -80
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
  386. nautobot/project-static/docs/development/core/generic-views.html +884 -80
  387. nautobot/project-static/docs/development/core/getting-started.html +884 -80
  388. nautobot/project-static/docs/development/core/homepage.html +884 -80
  389. nautobot/project-static/docs/development/core/index.html +884 -91
  390. nautobot/project-static/docs/development/core/model-checklist.html +887 -81
  391. nautobot/project-static/docs/development/core/model-features.html +884 -80
  392. nautobot/project-static/docs/development/core/natural-keys.html +884 -80
  393. nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
  394. nautobot/project-static/docs/development/core/release-checklist.html +887 -83
  395. nautobot/project-static/docs/development/core/role-internals.html +884 -80
  396. nautobot/project-static/docs/development/core/settings.html +884 -80
  397. nautobot/project-static/docs/development/core/style-guide.html +885 -81
  398. nautobot/project-static/docs/development/core/templates.html +896 -81
  399. nautobot/project-static/docs/development/core/testing.html +884 -80
  400. nautobot/project-static/docs/development/core/user-preferences.html +884 -80
  401. nautobot/project-static/docs/development/index.html +884 -80
  402. nautobot/project-static/docs/development/jobs/index.html +1247 -457
  403. nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
  404. nautobot/project-static/docs/index.html +13 -8228
  405. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  406. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  407. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  408. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  409. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  410. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  411. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  412. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  413. nautobot/project-static/docs/models/dcim/module.html +15 -0
  414. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  415. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  416. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  417. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  418. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  419. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  420. nautobot/project-static/docs/models/extras/role.html +15 -0
  421. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  422. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  423. nautobot/project-static/docs/models/extras/status.html +15 -0
  424. nautobot/project-static/docs/objects.inv +0 -0
  425. nautobot/project-static/docs/overview/application_stack.html +892 -81
  426. nautobot/project-static/docs/overview/design_philosophy.html +886 -82
  427. nautobot/project-static/docs/overview/index.html +9032 -13
  428. nautobot/project-static/docs/release-notes/index.html +887 -83
  429. nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
  430. nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
  431. nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
  432. nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
  433. nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
  434. nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
  435. nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
  436. nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
  437. nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
  438. nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
  439. nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
  440. nautobot/project-static/docs/requirements.txt +4 -4
  441. nautobot/project-static/docs/search/search_index.json +1 -1
  442. nautobot/project-static/docs/sitemap.xml +335 -260
  443. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  444. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
  445. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
  447. nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
  448. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
  449. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
  450. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
  451. nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
  452. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
  453. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
  454. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
  455. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
  456. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
  457. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
  458. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
  459. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
  460. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
  461. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
  462. nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
  463. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
  464. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
  465. nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
  466. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
  467. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
  468. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
  471. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
  472. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
  473. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
  474. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
  475. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
  481. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
  482. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
  483. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
  487. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  488. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
  535. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
  536. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
  537. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
  539. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
  540. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
  541. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
  548. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
  549. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
  550. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
  551. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
  552. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
  553. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
  556. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
  557. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
  558. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
  559. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
  560. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
  561. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
  564. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
  567. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
  568. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
  569. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  570. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  571. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  572. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  592. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  593. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  594. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
  595. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
  596. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
  597. nautobot/project-static/docs/user-guide/index.html +884 -80
  598. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
  599. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
  600. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
  601. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
  602. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
  603. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
  604. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
  605. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
  606. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
  607. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
  608. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
  613. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
  614. nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
  615. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
  616. nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
  617. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
  632. nautobot/project-static/js/forms.js +71 -0
  633. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  634. nautobot/project-static/js/tableconfig.js +6 -1
  635. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  636. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  637. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  638. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  639. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  640. nautobot/tenancy/__init__.py +0 -1
  641. nautobot/tenancy/apps.py +1 -0
  642. nautobot/tenancy/factory.py +3 -2
  643. nautobot/tenancy/filters/__init__.py +1 -0
  644. nautobot/tenancy/forms.py +1 -1
  645. nautobot/tenancy/templates/tenancy/tenant.html +22 -18
  646. nautobot/tenancy/views.py +11 -10
  647. nautobot/users/__init__.py +0 -1
  648. nautobot/users/api/serializers.py +1 -1
  649. nautobot/users/api/views.py +4 -2
  650. nautobot/users/apps.py +3 -2
  651. nautobot/users/factory.py +3 -3
  652. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  653. nautobot/users/models.py +12 -0
  654. nautobot/users/tests/test_filters.py +6 -3
  655. nautobot/users/urls.py +8 -0
  656. nautobot/virtualization/__init__.py +0 -1
  657. nautobot/virtualization/apps.py +1 -0
  658. nautobot/virtualization/filters.py +6 -1
  659. nautobot/virtualization/forms.py +11 -3
  660. nautobot/virtualization/graphql/types.py +2 -2
  661. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  662. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  663. nautobot/virtualization/tables.py +15 -5
  664. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  665. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  666. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  667. nautobot/virtualization/tests/test_api.py +9 -4
  668. nautobot/virtualization/tests/test_filters.py +22 -0
  669. nautobot/virtualization/tests/test_models.py +7 -3
  670. nautobot/virtualization/tests/test_views.py +19 -3
  671. nautobot/virtualization/urls.py +2 -2
  672. nautobot/virtualization/views.py +10 -32
  673. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
  674. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
  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.0b1.dist-info}/LICENSE.txt +0 -0
  695. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
  696. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
  697. {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
@@ -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
 
@@ -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)
@@ -41,13 +41,14 @@ class EnhancedPage(Page):
41
41
  return page_list
42
42
 
43
43
 
44
- def get_paginate_count(request):
44
+ def get_paginate_count(request, saved_view=None):
45
45
  """
46
46
  Determine the length of a page, using the following in order:
47
47
 
48
48
  1. per_page URL query parameter
49
- 2. Saved user preference
50
- 3. PAGINATE_COUNT global setting.
49
+ 2. Saved view config
50
+ 3. Saved user preference
51
+ 4. PAGINATE_COUNT global setting.
51
52
  """
52
53
  if "per_page" in request.GET:
53
54
  try:
@@ -58,6 +59,11 @@ def get_paginate_count(request):
58
59
  except ValueError:
59
60
  pass
60
61
 
62
+ if saved_view is not None:
63
+ per_page = saved_view.config.get("pagination_count", None)
64
+ if per_page:
65
+ return per_page
66
+
61
67
  if request.user.is_authenticated:
62
68
  return request.user.get_config("pagination.per_page", config.get_settings_or_config("PAGINATE_COUNT"))
63
69
  return config.get_settings_or_config("PAGINATE_COUNT")
@@ -2,7 +2,9 @@ import logging
2
2
 
3
3
  from django.contrib import messages
4
4
  from django.contrib.contenttypes.models import ContentType
5
+ from django.core.exceptions import ObjectDoesNotExist
5
6
  from django.db.models import Q
7
+ from django.template import engines, loader
6
8
  from django_tables2 import RequestConfig
7
9
  from rest_framework import renderers
8
10
 
@@ -14,17 +16,20 @@ from nautobot.core.forms import (
14
16
  from nautobot.core.forms.forms import DynamicFilterFormSet
15
17
  from nautobot.core.templatetags.helpers import bettertitle, validated_viewname
16
18
  from nautobot.core.utils.config import get_settings_or_config
17
- from nautobot.core.utils.lookup import get_created_and_last_updated_usernames_for_model
18
19
  from nautobot.core.utils.permissions import get_permission_for_model
19
20
  from nautobot.core.utils.requests import (
20
21
  convert_querydict_to_factory_formset_acceptable_querydict,
21
22
  normalize_querydict,
22
23
  )
23
24
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
24
- from nautobot.core.views.utils import check_filter_for_display, get_csv_form_fields_from_serializer_class
25
+ from nautobot.core.views.utils import (
26
+ check_filter_for_display,
27
+ common_detail_view_context,
28
+ get_csv_form_fields_from_serializer_class,
29
+ view_changes_not_saved,
30
+ )
31
+ from nautobot.extras.models import SavedView
25
32
  from nautobot.extras.models.change_logging import ObjectChange
26
- from nautobot.extras.tables import AssociatedContactsTable
27
- from nautobot.extras.utils import get_base_template
28
33
 
29
34
 
30
35
  class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
@@ -34,6 +39,9 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
34
39
 
35
40
  # Log error messages within NautobotHTMLRenderer
36
41
  logger = logging.getLogger(__name__)
42
+ saved_view = None
43
+
44
+ exception_template_names = ["%(status_code)s.html"]
37
45
 
38
46
  def get_dynamic_filter_form(self, view, request, *args, filterset_class=None, **kwargs):
39
47
  """
@@ -44,7 +52,9 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
44
52
  filterset = None
45
53
  if filterset_class:
46
54
  filterset = filterset_class()
47
- factory_formset_params = convert_querydict_to_factory_formset_acceptable_querydict(request.GET, filterset)
55
+ factory_formset_params = convert_querydict_to_factory_formset_acceptable_querydict(
56
+ view.filter_params if view.filter_params is not None else request.GET, filterset
57
+ )
48
58
  return DynamicFilterFormSet(filterset=filterset, data=factory_formset_params)
49
59
 
50
60
  def construct_user_permissions(self, request, model):
@@ -64,14 +74,32 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
64
74
  """
65
75
  table_class = view.get_table_class()
66
76
  request = kwargs.get("request", view.request)
77
+ saved_view_pk = request.GET.get("saved_view", None)
78
+ table_changes_pending = request.GET.get("table_changes_pending", False)
67
79
  queryset = view.alter_queryset(request)
68
80
 
69
81
  if view.action in ["list", "notes", "changelog"]:
70
82
  if view.action == "list":
71
83
  permissions = kwargs.get("permissions", {})
72
- if view.request.GET.getlist("sort"):
84
+ self.saved_view = None
85
+ if saved_view_pk is not None:
86
+ try:
87
+ # We are not using .restrict(request.user, "view") here
88
+ # User should be able to see any saved view that he has the list view access to.
89
+ self.saved_view = SavedView.objects.get(pk=saved_view_pk)
90
+ except ObjectDoesNotExist:
91
+ pass
92
+ if view.request.GET.getlist("sort") or (
93
+ self.saved_view is not None and self.saved_view.config.get("sort_order")
94
+ ):
73
95
  view.hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
74
- table = table_class(queryset, user=request.user, hide_hierarchy_ui=view.hide_hierarchy_ui)
96
+ table = table_class(
97
+ queryset,
98
+ table_changes_pending=table_changes_pending,
99
+ saved_view=self.saved_view,
100
+ user=request.user,
101
+ hide_hierarchy_ui=view.hide_hierarchy_ui,
102
+ )
75
103
  if "pk" in table.base_columns and (permissions["change"] or permissions["delete"]):
76
104
  table.columns.show("pk")
77
105
  elif view.action == "notes":
@@ -93,7 +121,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
93
121
  # Apply the request context
94
122
  paginate = {
95
123
  "paginator_class": EnhancedPaginator,
96
- "per_page": get_paginate_count(request),
124
+ "per_page": get_paginate_count(request, self.saved_view),
97
125
  }
98
126
  max_page_size = get_settings_or_config("MAX_PAGE_SIZE")
99
127
  if max_page_size and paginate["per_page"] > max_page_size:
@@ -129,6 +157,30 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
129
157
  messages.error(request, f"Missing views for action(s) {', '.join(invalid_actions)}")
130
158
  return valid_actions
131
159
 
160
+ def get_template_context(self, data, renderer_context):
161
+ # borrowed from rest_framework's TemplateHTMLRenderer - should our html renderer be based on that class?
162
+ response = renderer_context["response"]
163
+ if response.exception:
164
+ data["status_code"] = response.status_code
165
+ return data
166
+
167
+ def get_exception_template(self, response):
168
+ # borrowed from rest_framework's TemplateHTMLRenderer - remove if switching base class
169
+ template_names = [name % {"status_code": response.status_code} for name in self.exception_template_names]
170
+
171
+ try:
172
+ # Try to find an appropriate error template
173
+ return self.resolve_template(template_names)
174
+ except Exception:
175
+ # Fall back to using eg '404 Not Found'
176
+ body = f"{response.status_code} {response.status_text.title()}"
177
+ template = engines["django"].from_string(body)
178
+ return template
179
+
180
+ def resolve_template(self, template_names):
181
+ # borrowed from rest_framework's TemplateHTMLRenderer - remove if switching base class
182
+ return loader.select_template(template_names)
183
+
132
184
  def get_context(self, data, accepted_media_type, renderer_context):
133
185
  """
134
186
  Override get_context() from BrowsableAPIRenderer to obtain the context data we need to render our templates.
@@ -157,7 +209,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
157
209
  display_filter_params = []
158
210
  # Compile a dictionary indicating which permissions are available to the current user for this model
159
211
  permissions = self.construct_user_permissions(request, model)
160
- if view.action in ["create", "retrieve", "update", "destroy", "changelog", "notes"]:
212
+ if view.detail or view.action == "create":
161
213
  instance = view.get_object()
162
214
  return_url = view.get_return_url(request, instance)
163
215
  else:
@@ -179,9 +231,9 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
179
231
  for field_name, values in view.filter_params.items()
180
232
  ]
181
233
  if view.filterset_form_class is not None:
182
- filter_form = view.filterset_form_class(request.GET, label_suffix="")
234
+ filter_form = view.filterset_form_class(view.filter_params, label_suffix="")
183
235
  table = self.construct_table(view, request=request, permissions=permissions)
184
- search_form = SearchForm(data=request.GET)
236
+ search_form = SearchForm(data=view.filter_params)
185
237
  elif view.action == "destroy":
186
238
  form = form_class(initial=request.GET)
187
239
  elif view.action in ["create", "update"]:
@@ -238,52 +290,56 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
238
290
  "verbose_name_plural": queryset.model._meta.verbose_name_plural,
239
291
  }
240
292
  if view.action == "retrieve":
241
- created_by, last_updated_by = get_created_and_last_updated_usernames_for_model(instance)
293
+ context.update(common_detail_view_context(request, instance))
294
+ elif view.action == "list":
295
+ # Construct valid actions for list view.
296
+ valid_actions = self.validate_action_buttons(view, request)
297
+ # Query SavedViews for dropdown button
298
+ list_url = validated_viewname(model, "list")
299
+ saved_views = None
300
+ if model.is_saved_view_model:
301
+ # We are not using .restrict(request.user, "view") here
302
+ # User should be able to see any saved view that he has the list view access to.
303
+ if request.user.has_perms(["extras.view_savedview"]):
304
+ saved_views = SavedView.objects.filter(view=list_url).order_by("name").only("pk", "name")
305
+ else:
306
+ shared_saved_views = (
307
+ SavedView.objects.filter(view=list_url, is_shared=True).order_by("name").only("pk", "name")
308
+ )
309
+ user_owned_saved_views = (
310
+ SavedView.objects.filter(view=list_url, owner=request.user).order_by("name").only("pk", "name")
311
+ )
312
+ saved_views = shared_saved_views | user_owned_saved_views
242
313
 
243
- context["created_by"] = created_by
244
- context["last_updated_by"] = last_updated_by
245
- if instance.is_contact_associable_model:
246
- paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
247
- associations = instance.associated_contacts.restrict(request.user, "view").order_by("role__name")
248
- associations_table = AssociatedContactsTable(associations, orderable=False)
249
- RequestConfig(request, paginate).configure(associations_table)
250
- associations_table.columns.show("pk")
251
- context["associated_contacts_table"] = associations_table
252
- else:
253
- context["associated_contacts_table"] = None
254
- context.update(view.get_extra_context(request, instance))
255
- else:
256
- if view.action == "list":
257
- # Construct valid actions for list view.
258
- valid_actions = self.validate_action_buttons(view, request)
259
- context.update(
260
- {
261
- "action_buttons": valid_actions,
262
- "list_url": validated_viewname(model, "list"),
263
- "title": bettertitle(model._meta.verbose_name_plural),
264
- }
265
- )
266
- elif view.action in ["create", "update"]:
267
- context.update(
268
- {
269
- "editing": instance.present_in_database,
270
- }
271
- )
272
- elif view.action == "bulk_create": # 3.0 TODO: remove, replaced by ImportObjects system Job
273
- context.update(
274
- {
275
- "active_tab": view.bulk_create_active_tab if view.bulk_create_active_tab else "csv-data",
276
- "fields": get_csv_form_fields_from_serializer_class(view.serializer_class),
277
- }
278
- )
279
- elif view.action in ["changelog", "notes"]:
280
- context.update(
281
- {
282
- "base_template": get_base_template(data.get("base_template"), model),
283
- "active_tab": view.action,
284
- }
285
- )
286
- context.update(view.get_extra_context(request, instance=None))
314
+ new_changes_not_applied = view_changes_not_saved(request, view, self.saved_view)
315
+ context.update(
316
+ {
317
+ "model": model,
318
+ "current_saved_view": self.saved_view,
319
+ "new_changes_not_applied": new_changes_not_applied,
320
+ "action_buttons": valid_actions,
321
+ "list_url": list_url,
322
+ "saved_views": saved_views,
323
+ "title": bettertitle(model._meta.verbose_name_plural),
324
+ }
325
+ )
326
+ elif view.action in ["create", "update"]:
327
+ context.update(
328
+ {
329
+ "editing": instance.present_in_database,
330
+ }
331
+ )
332
+ elif view.action == "bulk_create": # 3.0 TODO: remove, replaced by ImportObjects system Job
333
+ context.update(
334
+ {
335
+ "active_tab": view.bulk_create_active_tab if view.bulk_create_active_tab else "csv-data",
336
+ "fields": get_csv_form_fields_from_serializer_class(view.serializer_class),
337
+ }
338
+ )
339
+
340
+ # Ensure the proper inheritance of context variables is applied: the view's returned data takes priority over the viewset's get_extra_context
341
+ context.update(view.get_extra_context(request, instance))
342
+ context.update(self.get_template_context(data, renderer_context))
287
343
  return context
288
344
 
289
345
  def render(self, data, accepted_media_type=None, renderer_context=None):
@@ -291,6 +347,15 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
291
347
  Overrode render() from BrowsableAPIRenderer to set self.template with NautobotViewSet's get_template_name() before it is rendered.
292
348
  """
293
349
  view = renderer_context["view"]
350
+ request = renderer_context["request"]
351
+ response = renderer_context["response"]
352
+
353
+ # TODO: borrowed from TemplateHTMLRenderer. Remove when switching base class
354
+ if response.exception:
355
+ template = self.get_exception_template(response)
356
+ context = self.get_context(data, accepted_media_type, renderer_context)
357
+ return template.render(context, request=request)
358
+
294
359
  # Get the corresponding template based on self.action in view.get_template_name() unless it is already specified in the Response() data.
295
360
  # See form_valid() for self.action == "bulk_create".
296
361
  self.template = data.get("template", view.get_template_name())