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
nautobot/extras/views.py CHANGED
@@ -1,16 +1,19 @@
1
1
  import logging
2
+ from urllib.parse import parse_qs
2
3
 
3
4
  from celery import chain
4
5
  from django.contrib import messages
6
+ from django.contrib.auth.models import AnonymousUser
5
7
  from django.contrib.contenttypes.models import ContentType
6
8
  from django.core.exceptions import ObjectDoesNotExist, ValidationError
7
- from django.db import transaction
9
+ from django.db import IntegrityError, transaction
8
10
  from django.db.models import ProtectedError, Q
9
11
  from django.forms.utils import pretty_name
10
12
  from django.http import Http404, HttpResponse, HttpResponseForbidden
11
13
  from django.shortcuts import get_object_or_404, redirect, render
12
14
  from django.template.loader import get_template, TemplateDoesNotExist
13
15
  from django.urls import reverse
16
+ from django.urls.exceptions import NoReverseMatch
14
17
  from django.utils import timezone
15
18
  from django.utils.encoding import iri_to_uri
16
19
  from django.utils.html import format_html
@@ -18,38 +21,51 @@ from django.utils.http import url_has_allowed_host_and_scheme
18
21
  from django.views.generic import View
19
22
  from django_tables2 import RequestConfig
20
23
  from jsonschema.validators import Draft7Validator
24
+ from rest_framework.decorators import action
21
25
 
22
26
  from nautobot.core.forms import restrict_form_fields
23
27
  from nautobot.core.models.querysets import count_related
24
28
  from nautobot.core.models.utils import pretty_print_query
25
29
  from nautobot.core.tables import ButtonsColumn
26
- from nautobot.core.utils.lookup import get_table_for_model
30
+ from nautobot.core.utils.config import get_settings_or_config
31
+ from nautobot.core.utils.lookup import (
32
+ get_filterset_for_model,
33
+ get_route_for_model,
34
+ get_table_class_string_from_view_name,
35
+ get_table_for_model,
36
+ )
37
+ from nautobot.core.utils.permissions import get_permission_for_model
27
38
  from nautobot.core.utils.requests import normalize_querydict
28
39
  from nautobot.core.views import generic, viewsets
29
40
  from nautobot.core.views.mixins import (
41
+ GetReturnURLMixin,
30
42
  ObjectBulkDestroyViewMixin,
31
43
  ObjectBulkUpdateViewMixin,
44
+ ObjectChangeLogViewMixin,
32
45
  ObjectDestroyViewMixin,
46
+ ObjectDetailViewMixin,
33
47
  ObjectEditViewMixin,
48
+ ObjectListViewMixin,
34
49
  ObjectPermissionRequiredMixin,
35
50
  )
36
51
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
37
52
  from nautobot.core.views.utils import prepare_cloned_fields
38
53
  from nautobot.core.views.viewsets import NautobotUIViewSet
39
- from nautobot.dcim.models import Controller, Device, Interface, Location, Rack
40
- from nautobot.dcim.tables import ControllerTable, DeviceTable, RackTable
54
+ from nautobot.dcim.models import Controller, Device, Interface, Module, Rack
55
+ from nautobot.dcim.tables import ControllerTable, DeviceTable, InterfaceTable, ModuleTable, RackTable
41
56
  from nautobot.extras.constants import JOB_OVERRIDABLE_FIELDS
57
+ from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
42
58
  from nautobot.extras.signals import change_context_state
43
59
  from nautobot.extras.tasks import delete_custom_field_data
44
60
  from nautobot.extras.utils import get_base_template, get_worker_count
45
61
  from nautobot.ipam.models import IPAddress, Prefix, VLAN
46
62
  from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
47
63
  from nautobot.virtualization.models import VirtualMachine, VMInterface
48
- from nautobot.virtualization.tables import VirtualMachineTable
64
+ from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
49
65
 
50
66
  from . import filters, forms, tables
51
67
  from .api import serializers
52
- from .choices import JobExecutionType, JobResultStatusChoices, LogLevelChoices
68
+ from .choices import DynamicGroupTypeChoices, JobExecutionType, JobResultStatusChoices, LogLevelChoices
53
69
  from .datasources import (
54
70
  enqueue_git_repository_diff_origin_and_local,
55
71
  enqueue_pull_git_repository_and_refresh_data,
@@ -75,19 +91,24 @@ from .models import (
75
91
  JobHook,
76
92
  JobLogEntry,
77
93
  JobResult,
94
+ MetadataType,
78
95
  Note,
79
96
  ObjectChange,
97
+ ObjectMetadata,
80
98
  Relationship,
81
99
  RelationshipAssociation,
82
100
  Role,
101
+ SavedView,
83
102
  ScheduledJob,
84
103
  Secret,
85
104
  SecretsGroup,
86
105
  SecretsGroupAssociation,
106
+ StaticGroupAssociation,
87
107
  Status,
88
108
  Tag,
89
109
  TaggedItem,
90
110
  Team,
111
+ UserSavedViewAssociation,
91
112
  Webhook,
92
113
  )
93
114
  from .registry import registry
@@ -698,12 +719,19 @@ class DynamicGroupView(generic.ObjectView):
698
719
 
699
720
  def get_extra_context(self, request, instance):
700
721
  context = super().get_extra_context(request, instance)
701
- model = instance.content_type.model_class()
722
+ model = instance.model
702
723
  table_class = get_table_for_model(model)
703
724
 
725
+ if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
726
+ # Ensure that members cache is up-to-date for this specific group
727
+ members = instance.update_cached_members()
728
+ messages.success(request, f"Refreshed cached members list for {instance}")
729
+ else:
730
+ members = instance.members
731
+
704
732
  if table_class is not None:
705
733
  # Members table (for display on Members nav tab)
706
- members_table = table_class(instance.members.restrict(request.user, "view"), orderable=False)
734
+ members_table = table_class(members.restrict(request.user, "view"), orderable=False)
707
735
  paginate = {
708
736
  "paginator_class": EnhancedPaginator,
709
737
  "per_page": get_paginate_count(request),
@@ -723,7 +751,16 @@ class DynamicGroupView(generic.ObjectView):
723
751
  ancestors_table = tables.NestedDynamicGroupAncestorsTable(ancestors, orderable=False)
724
752
  ancestors_tree = instance.flatten_ancestors_tree(instance.ancestors_tree())
725
753
 
726
- context["raw_query"] = pretty_print_query(instance.generate_query())
754
+ if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
755
+ context["raw_query"] = pretty_print_query(instance.generate_query())
756
+ context["members_list_url"] = None
757
+ else:
758
+ context["raw_query"] = None
759
+ try:
760
+ context["members_list_url"] = reverse(get_route_for_model(instance.model, "list"))
761
+ except NoReverseMatch:
762
+ context["members_list_url"] = None
763
+ context["members_verbose_name_plural"] = instance.model._meta.verbose_name_plural
727
764
  context["members_table"] = members_table
728
765
  context["ancestors_table"] = ancestors_table
729
766
  context["ancestors_tree"] = ancestors_tree
@@ -775,16 +812,19 @@ class DynamicGroupEditView(generic.ObjectEditView):
775
812
  # Obtain the instance, but do not yet `save()` it to the database.
776
813
  obj = form.save(commit=False)
777
814
 
778
- # Process the filter form and save the query filters to `obj.filter`.
779
815
  ctx = self.get_extra_context(request, obj)
780
- filter_form = ctx["filter_form"]
781
- if filter_form.is_valid():
782
- obj.set_filter(filter_form.cleaned_data)
783
- else:
784
- raise RuntimeError(filter_form.errors)
816
+ if obj.group_type == DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER:
817
+ # Process the filter form and save the query filters to `obj.filter`.
818
+ filter_form = ctx["filter_form"]
819
+ if filter_form.is_valid():
820
+ obj.set_filter(filter_form.cleaned_data)
821
+ else:
822
+ raise RuntimeError(filter_form.errors)
785
823
 
786
824
  # After filters have been set, now we save the object to the database.
787
825
  obj.save()
826
+ # Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
827
+ form.save_m2m()
788
828
  # Check that the new object conforms with any assigned object-level permissions
789
829
  self.queryset.get(pk=obj.pk)
790
830
 
@@ -867,6 +907,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
867
907
  filterset = filters.DynamicGroupFilterSet
868
908
 
869
909
 
910
+ # 3.0 TODO: remove, deprecated since 2.3 (#5845)
870
911
  class ObjectDynamicGroupsView(generic.GenericView):
871
912
  """
872
913
  Present a list of dynamic groups associated to a particular object.
@@ -883,16 +924,18 @@ class ObjectDynamicGroupsView(generic.GenericView):
883
924
  obj = get_object_or_404(model, **kwargs)
884
925
 
885
926
  # Gather all dynamic groups for this object (and its related objects)
886
- dynamicsgroups_table = tables.DynamicGroupTable(
887
- data=obj.dynamic_groups_cached.restrict(request.user, "view"), orderable=False
927
+ dynamicgroups_table = tables.DynamicGroupTable(
928
+ data=obj.dynamic_groups.restrict(request.user, "view"), orderable=False
888
929
  )
930
+ dynamicgroups_table.columns.hide("content_type")
931
+ dynamicgroups_table.columns.hide("members")
889
932
 
890
933
  # Apply the request context
891
934
  paginate = {
892
935
  "paginator_class": EnhancedPaginator,
893
936
  "per_page": get_paginate_count(request),
894
937
  }
895
- RequestConfig(request, paginate).configure(dynamicsgroups_table)
938
+ RequestConfig(request, paginate).configure(dynamicgroups_table)
896
939
 
897
940
  self.base_template = get_base_template(self.base_template, model)
898
941
 
@@ -903,7 +946,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
903
946
  "object": obj,
904
947
  "verbose_name": obj._meta.verbose_name,
905
948
  "verbose_name_plural": obj._meta.verbose_name_plural,
906
- "table": dynamicsgroups_table,
949
+ "table": dynamicgroups_table,
907
950
  "base_template": self.base_template,
908
951
  "active_tab": "dynamic-groups",
909
952
  },
@@ -1583,6 +1626,257 @@ class JobApprovalRequestView(generic.ObjectView):
1583
1626
  )
1584
1627
 
1585
1628
 
1629
+ #
1630
+ # Saved Views
1631
+ #
1632
+
1633
+
1634
+ class SavedViewUIViewSet(
1635
+ ObjectDetailViewMixin,
1636
+ ObjectChangeLogViewMixin,
1637
+ ObjectDestroyViewMixin,
1638
+ ObjectEditViewMixin,
1639
+ ObjectListViewMixin,
1640
+ ):
1641
+ queryset = SavedView.objects.all()
1642
+ form_class = forms.SavedViewForm
1643
+ filterset_class = filters.SavedViewFilterSet
1644
+ serializer_class = serializers.SavedViewSerializer
1645
+ table_class = tables.SavedViewTable
1646
+ action_buttons = ("export",)
1647
+
1648
+ def alter_queryset(self, request):
1649
+ """
1650
+ Two scenarios we need to handle here:
1651
+ 1. User can view all saved views with extras.view_savedview permission.
1652
+ 2. User without the permission can only view shared savedviews and his/her own saved views.
1653
+ """
1654
+ queryset = super().alter_queryset(request)
1655
+ user = request.user
1656
+ if user.has_perms(["extras.view_savedview"]):
1657
+ saved_views = queryset.restrict(user, "view")
1658
+ else:
1659
+ shared_saved_views = queryset.filter(is_shared=True)
1660
+ user_owned_saved_views = queryset.filter(owner=user)
1661
+ saved_views = shared_saved_views | user_owned_saved_views
1662
+ return saved_views
1663
+
1664
+ def get_queryset(self):
1665
+ """
1666
+ Get the list of items for this view.
1667
+ All users should be able to see saved views so we do not apply extra permissions.
1668
+ """
1669
+ return self.queryset.all()
1670
+
1671
+ def check_permissions(self, request):
1672
+ """
1673
+ Override this method to not check any permissions.
1674
+ Since users with <app_label>.view_<model_name> permissions should be able to view saved views related to this model.
1675
+ And those permissions will be enforced in the related view.
1676
+ """
1677
+
1678
+ def dispatch(self, request, *args, **kwargs):
1679
+ if isinstance(request.user, AnonymousUser):
1680
+ return self.handle_no_permission()
1681
+ return super().dispatch(request, *args, **kwargs)
1682
+
1683
+ def extra_message_context(self, obj):
1684
+ """
1685
+ Context variables for this extra message.
1686
+ """
1687
+ return {"new_global_default_view": obj}
1688
+
1689
+ def extra_message(self, **kwargs):
1690
+ new_global_default_view = kwargs.get("new_global_default_view")
1691
+ view_name = new_global_default_view.view
1692
+ message = ""
1693
+ if new_global_default_view.is_global_default:
1694
+ message = format_html(
1695
+ '<br>The global default saved view for "{}" is set to <a href="{}">{}</a>',
1696
+ view_name,
1697
+ new_global_default_view.get_absolute_url(),
1698
+ new_global_default_view.name,
1699
+ )
1700
+ return message
1701
+
1702
+ def list(self, request, *args, **kwargs):
1703
+ if not request.user.has_perms(["extras.view_savedview"]):
1704
+ return self.handle_no_permission()
1705
+ return super().list(request, *args, **kwargs)
1706
+
1707
+ def retrieve(self, request, *args, **kwargs):
1708
+ """
1709
+ The detail view for a saved view should the related ObjectListView with saved configurations applied
1710
+ """
1711
+ instance = self.get_object()
1712
+ list_view_url = reverse(instance.view) + f"?saved_view={instance.pk}"
1713
+ return redirect(list_view_url)
1714
+
1715
+ @action(detail=True, name="Set Default", methods=["get"], url_path="set-default", url_name="set_default")
1716
+ def set_default(self, request, *args, **kwargs):
1717
+ """
1718
+ Set current saved view as the the request.user default view. Overriding the global default view if there is one.
1719
+ """
1720
+ user = request.user
1721
+ sv = SavedView.objects.get(pk=kwargs.get("pk", None))
1722
+ UserSavedViewAssociation.objects.filter(user=user, view_name=sv.view).delete()
1723
+ UserSavedViewAssociation.objects.create(user=user, saved_view=sv, view_name=sv.view)
1724
+ list_view_url = sv.get_absolute_url()
1725
+ messages.success(
1726
+ request, f"Successfully set current view '{sv.name}' as the default '{sv.view}' view for user {user}"
1727
+ )
1728
+ return redirect(list_view_url)
1729
+
1730
+ @action(detail=True, name="Update Config", methods=["get"], url_path="update-config", url_name="update_config")
1731
+ def update_saved_view_config(self, request, *args, **kwargs):
1732
+ """
1733
+ Extract filter_params, pagination and sort_order from request.GET and apply it to the SavedView specified
1734
+ """
1735
+ sv = SavedView.objects.get(pk=kwargs.get("pk", None))
1736
+ if sv.owner == request.user or request.user.has_perms(["extras.change_savedview"]):
1737
+ pass
1738
+ else:
1739
+ messages.error(
1740
+ request, f"You do not have the required permission to modify this Saved View owned by {sv.owner}"
1741
+ )
1742
+ return redirect(self.get_return_url(request, obj=sv))
1743
+ table_changes_pending = request.GET.get("table_changes_pending", False)
1744
+ all_filters_removed = request.GET.get("all_filters_removed", False)
1745
+ pagination_count = request.GET.get("per_page", None)
1746
+ if pagination_count is not None:
1747
+ sv.config["pagination_count"] = int(pagination_count)
1748
+ sort_order = request.GET.getlist("sort", [])
1749
+ if sort_order:
1750
+ sv.config["sort_order"] = sort_order
1751
+
1752
+ filter_params = {}
1753
+ for key in request.GET:
1754
+ if key in self.non_filter_params:
1755
+ continue
1756
+ # TODO: this is fragile, other single-value filters will also be unhappy if given a list
1757
+ if key == "q":
1758
+ filter_params[key] = request.GET.get(key)
1759
+ else:
1760
+ filter_params[key] = request.GET.getlist(key)
1761
+
1762
+ if filter_params:
1763
+ sv.config["filter_params"] = filter_params
1764
+ elif all_filters_removed:
1765
+ sv.config["filter_params"] = {}
1766
+
1767
+ if table_changes_pending:
1768
+ table_class = get_table_class_string_from_view_name(sv.view)
1769
+ if table_class:
1770
+ if sv.config.get("table_config", None) is None:
1771
+ sv.config["table_config"] = {}
1772
+ sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
1773
+
1774
+ sv.validated_save()
1775
+ list_view_url = sv.get_absolute_url()
1776
+ messages.success(request, f"Successfully updated current view {sv.name}")
1777
+ return redirect(list_view_url)
1778
+
1779
+ def create(self, request, *args, **kwargs):
1780
+ """
1781
+ This method will extract filter_params, pagination and sort_order from request.GET
1782
+ and the name of the new SavedView from request.POST to create a new SavedView.
1783
+ """
1784
+ name = request.POST.get("name")
1785
+ is_shared = request.POST.get("is_shared", False)
1786
+ if is_shared:
1787
+ is_shared = True
1788
+ params = request.POST.get("params", "")
1789
+
1790
+ param_dict = parse_qs(params)
1791
+
1792
+ single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "q", "per_page"]
1793
+ for key in param_dict.keys():
1794
+ if key in single_value_params:
1795
+ param_dict[key] = param_dict[key][0]
1796
+
1797
+ derived_view_pk = param_dict.get("saved_view", None)
1798
+ derived_instance = None
1799
+ if derived_view_pk:
1800
+ derived_instance = self.get_queryset().get(pk=derived_view_pk)
1801
+ view_name = request.POST.get("view")
1802
+ try:
1803
+ reverse(view_name)
1804
+ except NoReverseMatch:
1805
+ messages.error(request, f"Invalid view name {view_name} specified.")
1806
+ if derived_view_pk:
1807
+ return redirect(self.get_return_url(request, obj=derived_instance))
1808
+ else:
1809
+ return redirect(self.get_return_url(request))
1810
+ table_changes_pending = param_dict.get("table_changes_pending", False)
1811
+ all_filters_removed = param_dict.get("all_filters_removed", False)
1812
+ try:
1813
+ sv = SavedView.objects.create(name=name, owner=request.user, view=view_name, is_shared=is_shared)
1814
+ except IntegrityError:
1815
+ messages.error(request, f"You already have a Saved View named '{name}' for this view '{view_name}'")
1816
+ if derived_view_pk:
1817
+ return redirect(self.get_return_url(request, obj=derived_instance))
1818
+ else:
1819
+ return redirect(reverse(view_name))
1820
+ pagination_count = param_dict.get("per_page", None)
1821
+ if not pagination_count:
1822
+ if derived_instance and derived_instance.config.get("pagination_count", None):
1823
+ pagination_count = derived_instance.config["pagination_count"]
1824
+ else:
1825
+ pagination_count = get_settings_or_config("PAGINATE_COUNT")
1826
+ sv.config["pagination_count"] = int(pagination_count)
1827
+ sort_order = param_dict.get("sort", [])
1828
+ if not sort_order:
1829
+ if derived_instance:
1830
+ sort_order = derived_instance.config.get("sort_order", [])
1831
+ sv.config["sort_order"] = sort_order
1832
+
1833
+ sv.config["filter_params"] = {}
1834
+ for key in param_dict:
1835
+ if key in [*self.non_filter_params, "view"]:
1836
+ continue
1837
+ sv.config["filter_params"][key] = param_dict.get(key)
1838
+ if not sv.config["filter_params"]:
1839
+ if derived_instance and all_filters_removed:
1840
+ sv.config["filter_params"] = {}
1841
+ elif derived_instance:
1842
+ sv.config["filter_params"] = derived_instance.config["filter_params"]
1843
+
1844
+ table_class = get_table_class_string_from_view_name(view_name)
1845
+ sv.config["table_config"] = {}
1846
+ if table_class:
1847
+ if table_changes_pending or derived_instance is None:
1848
+ sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
1849
+ elif derived_instance.config.get("table_config") and derived_instance.config["table_config"].get(
1850
+ f"{table_class}"
1851
+ ):
1852
+ sv.config["table_config"][f"{table_class}"] = derived_instance.config["table_config"][f"{table_class}"]
1853
+ try:
1854
+ sv.validated_save()
1855
+ list_view_url = sv.get_absolute_url()
1856
+ message = f"Successfully created new Saved View '{sv.name}'."
1857
+ messages.success(request, message)
1858
+ return redirect(list_view_url)
1859
+ except ValidationError as e:
1860
+ messages.error(request, e)
1861
+ return redirect(self.get_return_url(request))
1862
+
1863
+ def destroy(self, request, *args, **kwargs):
1864
+ """
1865
+ request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response.
1866
+ request.POST: call perform_destroy() which validates the form and perform the action of delete.
1867
+ Override to add more variables to Response
1868
+ """
1869
+ sv = SavedView.objects.get(pk=kwargs.get("pk", None))
1870
+ if sv.owner == request.user or request.user.has_perms(["extras.delete_savedview"]):
1871
+ pass
1872
+ else:
1873
+ messages.error(
1874
+ request, f"You do not have the required permission to delete this Saved View owned by {sv.owner}"
1875
+ )
1876
+ return redirect(self.get_return_url(request, obj=sv))
1877
+ return super().destroy(request, *args, **kwargs)
1878
+
1879
+
1586
1880
  class ScheduledJobListView(generic.ObjectListView):
1587
1881
  queryset = ScheduledJob.objects.enabled()
1588
1882
  table = tables.ScheduledJobTable
@@ -1882,6 +2176,58 @@ class ObjectChangeLogView(generic.GenericView):
1882
2176
  )
1883
2177
 
1884
2178
 
2179
+ #
2180
+ # Metadata
2181
+ #
2182
+
2183
+
2184
+ class MetadataTypeUIViewSet(NautobotUIViewSet):
2185
+ bulk_update_form_class = forms.MetadataTypeBulkEditForm
2186
+ filterset_class = filters.MetadataTypeFilterSet
2187
+ filterset_form_class = forms.MetadataTypeFilterForm
2188
+ form_class = forms.MetadataTypeForm
2189
+ queryset = MetadataType.objects.all()
2190
+ serializer_class = serializers.MetadataTypeSerializer
2191
+ table_class = tables.MetadataTypeTable
2192
+
2193
+ def get_extra_context(self, request, instance):
2194
+ context = super().get_extra_context(request, instance)
2195
+
2196
+ if self.action in ("create", "update"):
2197
+ if request.POST:
2198
+ context["choices"] = forms.MetadataChoiceFormSet(data=request.POST, instance=instance)
2199
+ else:
2200
+ context["choices"] = forms.MetadataChoiceFormSet(instance=instance)
2201
+
2202
+ return context
2203
+
2204
+ def form_save(self, form, **kwargs):
2205
+ obj = super().form_save(form, **kwargs)
2206
+
2207
+ # Process the formset for choices
2208
+ ctx = self.get_extra_context(self.request, obj)
2209
+ choices = ctx["choices"]
2210
+ if choices.is_valid():
2211
+ choices.save()
2212
+ else:
2213
+ raise ValidationError(choices.errors)
2214
+
2215
+ return obj
2216
+
2217
+
2218
+ class ObjectMetadataUIViewSet(
2219
+ ObjectChangeLogViewMixin,
2220
+ ObjectDetailViewMixin,
2221
+ ObjectListViewMixin,
2222
+ ):
2223
+ filterset_class = filters.ObjectMetadataFilterSet
2224
+ filterset_form_class = forms.ObjectMetadataFilterForm
2225
+ queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "assigned_object_id", "scoped_fields")
2226
+ serializer_class = serializers.ObjectMetadataSerializer
2227
+ table_class = tables.ObjectMetadataTable
2228
+ action_buttons = ("export",)
2229
+
2230
+
1885
2231
  #
1886
2232
  # Notes
1887
2233
  #
@@ -2041,43 +2387,32 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
2041
2387
  }
2042
2388
 
2043
2389
  if ContentType.objects.get_for_model(Device) in context["content_types"]:
2044
- devices = instance.devices.select_related(
2045
- "status",
2046
- "location",
2047
- "tenant",
2048
- "role",
2049
- "rack",
2050
- "device_type",
2051
- ).restrict(request.user, "view")
2390
+ devices = instance.devices.restrict(request.user, "view")
2052
2391
  device_table = DeviceTable(devices)
2053
2392
  device_table.columns.hide("role")
2054
2393
  RequestConfig(request, paginate).configure(device_table)
2055
2394
  context["device_table"] = device_table
2056
2395
 
2396
+ if ContentType.objects.get_for_model(Interface) in context["content_types"]:
2397
+ interfaces = instance.interfaces.restrict(request.user, "view")
2398
+ interface_table = InterfaceTable(interfaces)
2399
+ interface_table.columns.hide("role")
2400
+ RequestConfig(request, paginate).configure(interface_table)
2401
+ context["interface_table"] = interface_table
2402
+
2057
2403
  if ContentType.objects.get_for_model(Controller) in context["content_types"]:
2058
- controllers = instance.controllers.select_related(
2059
- "status",
2060
- "location",
2061
- "tenant",
2062
- "role",
2063
- ).restrict(request.user, "view")
2404
+ controllers = instance.controllers.restrict(request.user, "view")
2064
2405
  controller_table = ControllerTable(controllers)
2065
2406
  controller_table.columns.hide("role")
2066
2407
  RequestConfig(request, paginate).configure(controller_table)
2067
2408
  context["controller_table"] = controller_table
2068
2409
 
2069
2410
  if ContentType.objects.get_for_model(IPAddress) in context["content_types"]:
2070
- ipaddress = (
2071
- instance.ip_addresses.select_related("status", "tenant")
2072
- .restrict(request.user, "view")
2073
- .annotate(
2074
- interface_count=count_related(Interface, "ip_addresses"),
2075
- interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
2076
- vm_interface_count=count_related(VMInterface, "ip_addresses"),
2077
- vm_interface_parent_count=count_related(
2078
- VirtualMachine, "interfaces__ip_addresses", distinct=True
2079
- ),
2080
- )
2411
+ ipaddress = instance.ip_addresses.restrict(request.user, "view").annotate(
2412
+ interface_count=count_related(Interface, "ip_addresses"),
2413
+ interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
2414
+ vm_interface_count=count_related(VMInterface, "ip_addresses"),
2415
+ vm_interface_parent_count=count_related(VirtualMachine, "interfaces__ip_addresses", distinct=True),
2081
2416
  )
2082
2417
  ipaddress_table = IPAddressTable(ipaddress)
2083
2418
  ipaddress_table.columns.hide("role")
@@ -2085,57 +2420,41 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
2085
2420
  context["ipaddress_table"] = ipaddress_table
2086
2421
 
2087
2422
  if ContentType.objects.get_for_model(Prefix) in context["content_types"]:
2088
- prefixes = (
2089
- instance.prefixes.select_related(
2090
- "status",
2091
- "tenant",
2092
- "vlan",
2093
- "namespace",
2094
- )
2095
- .restrict(request.user, "view")
2096
- .annotate(location_count=count_related(Location, "prefixes"))
2097
- )
2423
+ prefixes = instance.prefixes.restrict(request.user, "view")
2098
2424
  prefix_table = PrefixTable(prefixes)
2099
2425
  prefix_table.columns.hide("role")
2100
2426
  RequestConfig(request, paginate).configure(prefix_table)
2101
2427
  context["prefix_table"] = prefix_table
2102
2428
  if ContentType.objects.get_for_model(Rack) in context["content_types"]:
2103
- racks = instance.racks.select_related(
2104
- "location",
2105
- "status",
2106
- "tenant",
2107
- "rack_group",
2108
- ).restrict(request.user, "view")
2429
+ racks = instance.racks.restrict(request.user, "view")
2109
2430
  rack_table = RackTable(racks)
2110
2431
  rack_table.columns.hide("role")
2111
2432
  RequestConfig(request, paginate).configure(rack_table)
2112
2433
  context["rack_table"] = rack_table
2113
2434
  if ContentType.objects.get_for_model(VirtualMachine) in context["content_types"]:
2114
- virtual_machines = instance.virtual_machines.select_related(
2115
- "cluster",
2116
- "role",
2117
- "status",
2118
- "tenant",
2119
- ).restrict(request.user, "view")
2435
+ virtual_machines = instance.virtual_machines.restrict(request.user, "view")
2120
2436
  virtual_machine_table = VirtualMachineTable(virtual_machines)
2121
2437
  virtual_machine_table.columns.hide("role")
2122
2438
  RequestConfig(request, paginate).configure(virtual_machine_table)
2123
2439
  context["virtual_machine_table"] = virtual_machine_table
2124
-
2440
+ if ContentType.objects.get_for_model(VMInterface) in context["content_types"]:
2441
+ vm_interfaces = instance.vm_interfaces.restrict(request.user, "view")
2442
+ vminterface_table = VMInterfaceTable(vm_interfaces)
2443
+ vminterface_table.columns.hide("role")
2444
+ RequestConfig(request, paginate).configure(vminterface_table)
2445
+ context["vminterface_table"] = vminterface_table
2125
2446
  if ContentType.objects.get_for_model(VLAN) in context["content_types"]:
2126
- vlans = (
2127
- instance.vlans.annotate(location_count=count_related(Location, "vlans"))
2128
- .select_related(
2129
- "vlan_group",
2130
- "status",
2131
- "tenant",
2132
- )
2133
- .restrict(request.user, "view")
2134
- )
2447
+ vlans = instance.vlans.restrict(request.user, "view")
2135
2448
  vlan_table = VLANTable(vlans)
2136
2449
  vlan_table.columns.hide("role")
2137
2450
  RequestConfig(request, paginate).configure(vlan_table)
2138
2451
  context["vlan_table"] = vlan_table
2452
+ if ContentType.objects.get_for_model(Module) in context["content_types"]:
2453
+ modules = instance.modules.restrict(request.user, "view")
2454
+ module_table = ModuleTable(modules)
2455
+ module_table.columns.hide("role")
2456
+ RequestConfig(request, paginate).configure(module_table)
2457
+ context["module_table"] = module_table
2139
2458
  return context
2140
2459
 
2141
2460
 
@@ -2334,6 +2653,153 @@ class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
2334
2653
  table = tables.SecretsGroupTable
2335
2654
 
2336
2655
 
2656
+ #
2657
+ # Static Groups
2658
+ #
2659
+
2660
+
2661
+ class StaticGroupAssociationUIViewSet(
2662
+ ObjectBulkDestroyViewMixin,
2663
+ ObjectChangeLogViewMixin,
2664
+ ObjectDestroyViewMixin,
2665
+ ObjectDetailViewMixin,
2666
+ ObjectListViewMixin,
2667
+ # TODO anything else?
2668
+ ):
2669
+ filterset_class = filters.StaticGroupAssociationFilterSet
2670
+ filterset_form_class = forms.StaticGroupAssociationFilterForm
2671
+ queryset = StaticGroupAssociation.all_objects.all()
2672
+ serializer_class = serializers.StaticGroupAssociationSerializer
2673
+ table_class = tables.StaticGroupAssociationTable
2674
+ action_buttons = ("export",)
2675
+
2676
+ def alter_queryset(self, request):
2677
+ queryset = super().alter_queryset(request)
2678
+ # Default to only showing associations for static-type groups:
2679
+ if request is None or "dynamic_group" not in request.GET:
2680
+ queryset = queryset.filter(dynamic_group__group_type=DynamicGroupTypeChoices.TYPE_STATIC)
2681
+ return queryset
2682
+
2683
+
2684
+ class DynamicGroupBulkAssignView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
2685
+ queryset = StaticGroupAssociation.objects.all()
2686
+ form_class = forms.DynamicGroupBulkAssignForm
2687
+
2688
+ def get_required_permission(self):
2689
+ return get_permission_for_model(self.queryset.model, "add")
2690
+
2691
+ def get(self, request):
2692
+ return redirect(self.get_return_url(request))
2693
+
2694
+ def post(self, request, **kwargs):
2695
+ """
2696
+ Update the static group assignments of the provided `pk_list` (or `_all`) of the given `content_type`.
2697
+
2698
+ Unlike BulkEditView, this takes a single POST rather than two to perform its operation as
2699
+ there's no separate confirmation step involved.
2700
+ """
2701
+ # TODO more error handling - content-type doesn't exist, model_class not found, filterset missing, etc.
2702
+ content_type = ContentType.objects.get(pk=request.POST.get("content_type"))
2703
+ model = content_type.model_class()
2704
+ self.default_return_url = get_route_for_model(model, "list")
2705
+ filterset_class = get_filterset_for_model(model)
2706
+
2707
+ if request.POST.get("_all"):
2708
+ if filterset_class is not None:
2709
+ pk_list = list(filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True))
2710
+ else:
2711
+ pk_list = list(model.objects.all().values_list("pk", flat=True))
2712
+ else:
2713
+ pk_list = request.POST.getlist("pk")
2714
+
2715
+ form = self.form_class(model, request.POST)
2716
+ restrict_form_fields(form, request.user)
2717
+
2718
+ if form.is_valid():
2719
+ logger.debug("Form validation was successful")
2720
+ try:
2721
+ with transaction.atomic():
2722
+ add_to_groups = list(form.cleaned_data["add_to_groups"])
2723
+ new_group_name = form.cleaned_data["create_and_assign_to_new_group_name"]
2724
+ if new_group_name:
2725
+ if not request.user.has_perm("extras.add_dynamicgroup"):
2726
+ raise DynamicGroup.DoesNotExist
2727
+ else:
2728
+ new_group = DynamicGroup(
2729
+ name=new_group_name,
2730
+ content_type=content_type,
2731
+ group_type=DynamicGroupTypeChoices.TYPE_STATIC,
2732
+ )
2733
+ new_group.validated_save()
2734
+ # Check permissions
2735
+ DynamicGroup.objects.restrict(request.user, "add").get(pk=new_group.pk)
2736
+
2737
+ add_to_groups.append(new_group)
2738
+ msg = "Created dynamic group"
2739
+ logger.info(f"{msg} {new_group} (PK: {new_group.pk})")
2740
+ msg = format_html('{} <a href="{}">{}</a>', msg, new_group.get_absolute_url(), new_group)
2741
+ messages.success(self.request, msg)
2742
+
2743
+ with deferred_change_logging_for_bulk_operation():
2744
+ associations = []
2745
+ for pk in pk_list:
2746
+ for dynamic_group in add_to_groups:
2747
+ association, created = StaticGroupAssociation.objects.get_or_create(
2748
+ dynamic_group=dynamic_group,
2749
+ associated_object_type_id=content_type.id,
2750
+ associated_object_id=pk,
2751
+ )
2752
+ association.validated_save()
2753
+ associations.append(association)
2754
+ if created:
2755
+ logger.debug("Created %s", association)
2756
+
2757
+ # Enforce object-level permissions
2758
+ if self.queryset.filter(pk__in=[assoc.pk for assoc in associations]).count() != len(
2759
+ associations
2760
+ ):
2761
+ raise StaticGroupAssociation.DoesNotExist
2762
+
2763
+ if associations:
2764
+ msg = (
2765
+ f"Added {len(pk_list)} {model._meta.verbose_name_plural} "
2766
+ f"to {len(add_to_groups)} dynamic group(s)."
2767
+ )
2768
+ logger.info(msg)
2769
+ messages.success(self.request, msg)
2770
+
2771
+ if form.cleaned_data["remove_from_groups"]:
2772
+ for dynamic_group in form.cleaned_data["remove_from_groups"]:
2773
+ (
2774
+ StaticGroupAssociation.objects.restrict(request.user, "delete")
2775
+ .filter(
2776
+ dynamic_group=dynamic_group,
2777
+ associated_object_type=content_type,
2778
+ associated_object_id__in=pk_list,
2779
+ )
2780
+ .delete()
2781
+ )
2782
+
2783
+ msg = (
2784
+ f"Removed {len(pk_list)} {model._meta.verbose_name_plural} from "
2785
+ f"{len(form.cleaned_data['remove_from_groups'])} dynamic group(s)."
2786
+ )
2787
+ logger.info(msg)
2788
+ messages.success(self.request, msg)
2789
+ except ValidationError as e:
2790
+ messages.error(self.request, e)
2791
+ except ObjectDoesNotExist:
2792
+ msg = "Static group association failed due to object-level permissions violation"
2793
+ logger.warning(msg)
2794
+ messages.error(self.request, msg)
2795
+
2796
+ else:
2797
+ logger.debug("Form validation failed")
2798
+ messages.error(self.request, form.errors)
2799
+
2800
+ return redirect(self.get_return_url(request))
2801
+
2802
+
2337
2803
  #
2338
2804
  # Custom statuses
2339
2805
  #