nautobot 2.4.13__py3-none-any.whl → 2.4.15__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 (463) hide show
  1. nautobot/apps/choices.py +8 -0
  2. nautobot/apps/ui.py +14 -0
  3. nautobot/core/api/views.py +2 -0
  4. nautobot/core/choices.py +4 -0
  5. nautobot/core/filters.py +21 -41
  6. nautobot/core/graphql/generators.py +8 -0
  7. nautobot/core/graphql/schema.py +30 -30
  8. nautobot/core/management/commands/check_job_approval_status.py +47 -0
  9. nautobot/core/management/commands/generate_test_data.py +1 -1
  10. nautobot/core/management/commands/migrate.py +90 -1
  11. nautobot/core/models/tree_queries.py +17 -0
  12. nautobot/core/settings.py +2 -2
  13. nautobot/core/settings.yaml +3 -3
  14. nautobot/core/tables.py +29 -6
  15. nautobot/core/templates/base_django.html +1 -1
  16. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  17. nautobot/core/templates/generic/object_list.html +17 -20
  18. nautobot/core/templates/inc/breadcrumbs.html +14 -0
  19. nautobot/core/templatetags/buttons.py +2 -4
  20. nautobot/core/templatetags/helpers.py +29 -6
  21. nautobot/core/templatetags/ui_framework.py +21 -0
  22. nautobot/core/testing/api.py +7 -0
  23. nautobot/core/testing/filters.py +20 -3
  24. nautobot/core/testing/forms.py +1 -1
  25. nautobot/core/tests/integration/test_filters.py +2 -2
  26. nautobot/core/tests/test_breadcrumbs.py +366 -0
  27. nautobot/core/tests/test_commands.py +40 -0
  28. nautobot/core/tests/test_filters.py +51 -1
  29. nautobot/core/tests/test_forms.py +1 -1
  30. nautobot/core/tests/test_graphql.py +4 -4
  31. nautobot/core/tests/test_titles.py +183 -0
  32. nautobot/core/tests/test_tree_queries.py +30 -0
  33. nautobot/core/tests/test_views.py +2 -2
  34. nautobot/core/tests/test_views_utils.py +1 -1
  35. nautobot/core/ui/breadcrumbs.py +538 -0
  36. nautobot/core/ui/bulk_buttons.py +53 -0
  37. nautobot/core/ui/object_detail.py +31 -8
  38. nautobot/core/ui/titles.py +127 -0
  39. nautobot/core/ui/utils.py +25 -0
  40. nautobot/core/utils/migrations.py +1 -1
  41. nautobot/core/views/__init__.py +1 -1
  42. nautobot/core/views/mixins.py +26 -1
  43. nautobot/core/views/renderers.py +20 -2
  44. nautobot/core/views/utils.py +14 -13
  45. nautobot/dcim/api/serializers.py +9 -0
  46. nautobot/dcim/choices.py +55 -0
  47. nautobot/dcim/constants.py +0 -16
  48. nautobot/dcim/factory.py +1 -1
  49. nautobot/dcim/filters/__init__.py +15 -3
  50. nautobot/dcim/forms.py +120 -7
  51. nautobot/dcim/management/commands/trace_paths.py +1 -1
  52. nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
  53. nautobot/dcim/models/device_component_templates.py +8 -0
  54. nautobot/dcim/models/device_components.py +31 -12
  55. nautobot/dcim/models/devices.py +1 -1
  56. nautobot/dcim/models/power.py +171 -10
  57. nautobot/dcim/models/racks.py +7 -4
  58. nautobot/dcim/tables/devices.py +2 -0
  59. nautobot/dcim/tables/devicetypes.py +1 -0
  60. nautobot/dcim/tables/power.py +30 -2
  61. nautobot/dcim/templates/dcim/device.html +2 -2
  62. nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
  63. nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
  64. nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
  65. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
  66. nautobot/dcim/templates/dcim/rack.html +2 -318
  67. nautobot/dcim/templates/dcim/rack_edit.html +2 -47
  68. nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
  69. nautobot/dcim/templates/dcim/rack_update.html +47 -0
  70. nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
  71. nautobot/dcim/tests/test_api.py +24 -4
  72. nautobot/dcim/tests/test_filters.py +91 -13
  73. nautobot/dcim/tests/test_models.py +262 -0
  74. nautobot/dcim/tests/test_views.py +20 -12
  75. nautobot/dcim/urls.py +2 -27
  76. nautobot/dcim/utils.py +13 -30
  77. nautobot/dcim/views.py +428 -146
  78. nautobot/extras/choices.py +12 -4
  79. nautobot/extras/factory.py +19 -20
  80. nautobot/extras/filters/__init__.py +3 -2
  81. nautobot/extras/filters/mixins.py +23 -7
  82. nautobot/extras/forms/__init__.py +2 -1
  83. nautobot/extras/forms/forms.py +71 -0
  84. nautobot/extras/forms/mixins.py +4 -2
  85. nautobot/extras/managers.py +4 -1
  86. nautobot/extras/migrations/0062_collect_roles_from_related_apps_roles.py +30 -7
  87. nautobot/extras/migrations/0124_add_joblogentry_index.py +16 -0
  88. nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
  89. nautobot/extras/models/customfields.py +53 -5
  90. nautobot/extras/models/datasources.py +1 -2
  91. nautobot/extras/models/jobs.py +13 -3
  92. nautobot/extras/models/relationships.py +55 -6
  93. nautobot/extras/plugins/views.py +24 -1
  94. nautobot/extras/secrets/__init__.py +1 -1
  95. nautobot/extras/tables.py +9 -0
  96. nautobot/extras/templates/extras/customfield.html +2 -129
  97. nautobot/extras/templates/extras/customfield_edit.html +2 -108
  98. nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
  99. nautobot/extras/templates/extras/customfield_update.html +108 -0
  100. nautobot/extras/templates/extras/graphqlquery.html +2 -97
  101. nautobot/extras/templates/extras/graphqlquery_list.html +1 -0
  102. nautobot/extras/templates/extras/graphqlquery_retrieve.html +97 -0
  103. nautobot/extras/templates/extras/inc/jobresult.html +7 -3
  104. nautobot/extras/templates/extras/jobresult.html +2 -155
  105. nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
  106. nautobot/extras/templates/extras/marketplace.html +5 -6
  107. nautobot/extras/templates/extras/note.html +2 -53
  108. nautobot/extras/templates/extras/note_retrieve.html +53 -0
  109. nautobot/extras/templates/extras/plugins_list.html +5 -6
  110. nautobot/extras/templates/extras/secretsgroup.html +2 -29
  111. nautobot/extras/templates/extras/secretsgroup_edit.html +2 -82
  112. nautobot/extras/templates/extras/secretsgroup_retrieve.html +29 -0
  113. nautobot/extras/templates/extras/secretsgroup_update.html +82 -0
  114. nautobot/extras/templatetags/custom_links.py +2 -2
  115. nautobot/extras/templatetags/job_buttons.py +1 -1
  116. nautobot/extras/templatetags/plugins.py +1 -1
  117. nautobot/extras/tests/integration/test_computedfields.py +2 -2
  118. nautobot/extras/tests/integration/test_customfields.py +14 -11
  119. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  120. nautobot/extras/tests/integration/test_notes.py +1 -1
  121. nautobot/extras/tests/integration/test_plugins.py +6 -6
  122. nautobot/extras/tests/integration/test_relationships.py +2 -2
  123. nautobot/extras/tests/test_customfields.py +115 -7
  124. nautobot/extras/tests/test_filters.py +9 -0
  125. nautobot/extras/tests/test_forms.py +2 -2
  126. nautobot/extras/tests/test_plugins.py +2 -3
  127. nautobot/extras/tests/test_relationships.py +14 -8
  128. nautobot/extras/tests/test_views.py +285 -2
  129. nautobot/extras/urls.py +5 -110
  130. nautobot/extras/utils.py +5 -2
  131. nautobot/extras/views.py +116 -311
  132. nautobot/ipam/api/views.py +69 -6
  133. nautobot/ipam/tables.py +8 -15
  134. nautobot/ipam/tests/migration/test_migrations.py +8 -8
  135. nautobot/ipam/tests/test_api.py +352 -2
  136. nautobot/ipam/tests/test_models.py +1 -1
  137. nautobot/project-static/docs/404.html +34 -34
  138. nautobot/project-static/docs/apps/index.html +34 -34
  139. nautobot/project-static/docs/apps/nautobot-apps.html +34 -34
  140. nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
  141. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js → bundle.50899def.min.js} +2 -2
  142. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js.map → bundle.50899def.min.js.map} +2 -2
  143. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css → main.7e37652d.min.css} +1 -1
  144. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css.map → main.7e37652d.min.css.map} +1 -1
  145. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +39 -34
  146. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +36 -34
  147. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +139 -54
  148. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +48 -38
  149. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +50 -40
  150. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +36 -34
  151. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +35 -34
  152. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +43 -39
  153. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +52 -42
  154. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +50 -41
  155. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +54 -44
  156. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +85 -93
  157. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +154 -62
  158. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +54 -46
  159. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +146 -87
  160. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +240 -70
  161. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +38 -35
  162. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +41 -35
  163. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +173 -52
  164. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +269 -85
  165. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2643
  166. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +36 -34
  167. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +165 -89
  168. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +161 -69
  169. nautobot/project-static/docs/development/apps/api/configuration-view.html +34 -34
  170. nautobot/project-static/docs/development/apps/api/database-backend-config.html +34 -34
  171. nautobot/project-static/docs/development/apps/api/models/django-admin.html +34 -34
  172. nautobot/project-static/docs/development/apps/api/models/global-search.html +34 -34
  173. nautobot/project-static/docs/development/apps/api/models/graphql.html +34 -34
  174. nautobot/project-static/docs/development/apps/api/models/index.html +34 -34
  175. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +34 -34
  176. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +34 -34
  177. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +34 -34
  178. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +34 -34
  179. nautobot/project-static/docs/development/apps/api/platform-features/index.html +34 -34
  180. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +34 -34
  181. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +34 -34
  182. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +34 -34
  183. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +34 -34
  184. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +34 -34
  185. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +34 -34
  186. nautobot/project-static/docs/development/apps/api/prometheus.html +34 -34
  187. nautobot/project-static/docs/development/apps/api/setup.html +34 -34
  188. nautobot/project-static/docs/development/apps/api/testing.html +34 -34
  189. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +34 -34
  190. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +34 -34
  191. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +34 -34
  192. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +34 -34
  193. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +34 -34
  194. nautobot/project-static/docs/development/apps/api/views/base-template.html +34 -34
  195. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +34 -34
  196. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +34 -34
  197. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +34 -34
  198. nautobot/project-static/docs/development/apps/api/views/index.html +34 -34
  199. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +34 -34
  200. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +42 -36
  201. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +34 -34
  202. nautobot/project-static/docs/development/apps/api/views/notes.html +34 -34
  203. nautobot/project-static/docs/development/apps/api/views/rest-api.html +34 -34
  204. nautobot/project-static/docs/development/apps/api/views/urls.html +34 -34
  205. nautobot/project-static/docs/development/apps/index.html +34 -34
  206. nautobot/project-static/docs/development/apps/migration/code-updates.html +34 -34
  207. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +34 -34
  208. nautobot/project-static/docs/development/apps/migration/from-v1.html +34 -34
  209. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +34 -34
  210. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +34 -34
  211. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +34 -34
  212. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +34 -34
  213. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +37 -37
  214. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
  215. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +34 -34
  216. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +34 -34
  217. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +34 -34
  218. nautobot/project-static/docs/development/apps/porting-from-netbox.html +37 -37
  219. nautobot/project-static/docs/development/core/application-registry.html +162 -133
  220. nautobot/project-static/docs/development/core/best-practices.html +34 -34
  221. nautobot/project-static/docs/development/core/bootstrap-ui.html +34 -34
  222. nautobot/project-static/docs/development/core/caching.html +34 -34
  223. nautobot/project-static/docs/development/core/controllers.html +34 -34
  224. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +34 -34
  225. nautobot/project-static/docs/development/core/generic-views.html +34 -34
  226. nautobot/project-static/docs/development/core/getting-started.html +34 -34
  227. nautobot/project-static/docs/development/core/homepage.html +34 -34
  228. nautobot/project-static/docs/development/core/index.html +34 -34
  229. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +34 -34
  230. nautobot/project-static/docs/development/core/model-checklist.html +34 -34
  231. nautobot/project-static/docs/development/core/model-features.html +34 -34
  232. nautobot/project-static/docs/development/core/natural-keys.html +34 -34
  233. nautobot/project-static/docs/development/core/navigation-menu.html +34 -34
  234. nautobot/project-static/docs/development/core/release-checklist.html +34 -34
  235. nautobot/project-static/docs/development/core/role-internals.html +34 -34
  236. nautobot/project-static/docs/development/core/settings.html +34 -34
  237. nautobot/project-static/docs/development/core/style-guide.html +34 -34
  238. nautobot/project-static/docs/development/core/templates.html +34 -34
  239. nautobot/project-static/docs/development/core/testing.html +34 -34
  240. nautobot/project-static/docs/development/core/ui-component-framework.html +724 -289
  241. nautobot/project-static/docs/development/core/user-preferences.html +34 -34
  242. nautobot/project-static/docs/development/index.html +34 -34
  243. nautobot/project-static/docs/development/jobs/getting-started.html +34 -34
  244. nautobot/project-static/docs/development/jobs/index.html +34 -34
  245. nautobot/project-static/docs/development/jobs/installation.html +34 -34
  246. nautobot/project-static/docs/development/jobs/job-extensions.html +34 -34
  247. nautobot/project-static/docs/development/jobs/job-logging.html +34 -34
  248. nautobot/project-static/docs/development/jobs/job-patterns.html +34 -34
  249. nautobot/project-static/docs/development/jobs/job-structure.html +34 -34
  250. nautobot/project-static/docs/development/jobs/migration/from-v1.html +34 -34
  251. nautobot/project-static/docs/development/jobs/testing.html +34 -34
  252. nautobot/project-static/docs/index.html +34 -34
  253. nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
  254. nautobot/project-static/docs/media/power_distribution.png +0 -0
  255. nautobot/project-static/docs/objects.inv +0 -0
  256. nautobot/project-static/docs/overview/application_stack.html +34 -34
  257. nautobot/project-static/docs/overview/design_philosophy.html +34 -34
  258. nautobot/project-static/docs/release-notes/index.html +34 -34
  259. nautobot/project-static/docs/release-notes/version-1.0.html +34 -34
  260. nautobot/project-static/docs/release-notes/version-1.1.html +34 -34
  261. nautobot/project-static/docs/release-notes/version-1.2.html +34 -34
  262. nautobot/project-static/docs/release-notes/version-1.3.html +34 -34
  263. nautobot/project-static/docs/release-notes/version-1.4.html +34 -34
  264. nautobot/project-static/docs/release-notes/version-1.5.html +34 -34
  265. nautobot/project-static/docs/release-notes/version-1.6.html +34 -34
  266. nautobot/project-static/docs/release-notes/version-2.0.html +34 -34
  267. nautobot/project-static/docs/release-notes/version-2.1.html +34 -34
  268. nautobot/project-static/docs/release-notes/version-2.2.html +34 -34
  269. nautobot/project-static/docs/release-notes/version-2.3.html +34 -34
  270. nautobot/project-static/docs/release-notes/version-2.4.html +402 -34
  271. nautobot/project-static/docs/requirements.txt +3 -3
  272. nautobot/project-static/docs/search/search_index.json +1 -1
  273. nautobot/project-static/docs/sitemap.xml +303 -299
  274. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  275. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +34 -34
  276. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +34 -34
  277. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +34 -34
  278. nautobot/project-static/docs/user-guide/administration/configuration/index.html +34 -34
  279. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +34 -34
  280. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +37 -37
  281. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +34 -34
  282. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +34 -34
  283. nautobot/project-static/docs/user-guide/administration/guides/docker.html +34 -34
  284. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +34 -34
  285. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +34 -34
  286. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +34 -34
  287. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +34 -34
  288. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +34 -34
  289. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +34 -34
  290. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +34 -34
  291. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +34 -34
  292. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +34 -34
  293. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +34 -34
  294. nautobot/project-static/docs/user-guide/administration/installation/index.html +34 -34
  295. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +34 -34
  296. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +34 -34
  297. nautobot/project-static/docs/user-guide/administration/installation/services.html +34 -34
  298. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +34 -34
  299. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +34 -34
  300. nautobot/project-static/docs/user-guide/administration/security/index.html +34 -34
  301. nautobot/project-static/docs/user-guide/administration/security/notices.html +34 -34
  302. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +296 -251
  303. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +34 -34
  304. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +34 -34
  305. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +34 -34
  306. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +34 -34
  307. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +34 -34
  308. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +34 -34
  309. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +34 -34
  310. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +34 -34
  311. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +34 -34
  312. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +34 -34
  313. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +34 -34
  314. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +34 -34
  315. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +34 -34
  316. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +34 -34
  317. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +34 -34
  318. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +34 -34
  319. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +34 -34
  320. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +34 -34
  321. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +34 -34
  322. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +34 -34
  323. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +34 -34
  324. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +34 -34
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +34 -34
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +34 -34
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +34 -34
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +34 -34
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +34 -34
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +34 -34
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +34 -34
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +34 -34
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +34 -34
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +34 -34
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +34 -34
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +34 -34
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +34 -34
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +34 -34
  339. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +34 -34
  340. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +37 -37
  341. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +34 -34
  342. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +34 -34
  343. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +42 -52
  344. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +34 -34
  345. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +34 -34
  346. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +34 -34
  347. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +34 -34
  348. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +37 -37
  349. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +34 -34
  350. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +34 -34
  351. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -34
  352. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +34 -34
  353. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +316 -39
  354. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +35 -35
  355. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +34 -34
  356. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +147 -37
  357. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +52 -35
  358. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +51 -34
  359. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +34 -34
  360. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +34 -34
  361. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +34 -34
  362. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +34 -34
  363. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +34 -34
  364. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +34 -34
  365. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +34 -34
  366. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +34 -34
  367. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +34 -34
  368. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +34 -34
  369. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +34 -34
  370. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +34 -34
  371. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +34 -34
  372. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +34 -34
  373. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +34 -34
  374. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +34 -34
  375. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +34 -34
  376. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +34 -34
  377. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +34 -34
  378. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +34 -34
  379. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +34 -34
  380. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +34 -34
  381. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +34 -34
  382. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +34 -34
  383. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +34 -34
  384. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +34 -34
  385. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +34 -34
  386. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +34 -34
  387. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +34 -34
  388. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +34 -34
  389. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +34 -34
  390. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +34 -34
  391. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +34 -34
  392. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +34 -34
  393. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +34 -34
  394. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +34 -34
  395. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +34 -34
  396. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +34 -34
  397. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +34 -34
  398. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +34 -34
  399. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +34 -34
  400. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +34 -34
  401. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +34 -34
  402. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +34 -34
  403. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +34 -34
  404. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +34 -34
  405. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +34 -34
  406. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +34 -34
  407. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +34 -34
  408. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +34 -34
  409. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +34 -34
  410. nautobot/project-static/docs/user-guide/index.html +34 -34
  411. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +34 -34
  412. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +34 -34
  413. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +34 -34
  414. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +34 -34
  415. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +34 -34
  416. nautobot/project-static/docs/user-guide/platform-functionality/events.html +34 -34
  417. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +34 -34
  418. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +34 -34
  419. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +34 -34
  420. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +34 -34
  421. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +34 -34
  422. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +34 -34
  423. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +34 -34
  424. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +35 -35
  425. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +34 -34
  426. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +34 -34
  427. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +34 -34
  428. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +34 -34
  429. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +34 -34
  430. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +34 -34
  431. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +34 -34
  432. nautobot/project-static/docs/user-guide/platform-functionality/note.html +34 -34
  433. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +34 -34
  434. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +34 -34
  435. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +34 -34
  436. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +34 -34
  437. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +34 -34
  438. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +34 -34
  439. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +34 -34
  440. nautobot/project-static/docs/user-guide/platform-functionality/role.html +34 -34
  441. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +34 -34
  442. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +34 -34
  443. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +34 -34
  444. nautobot/project-static/docs/user-guide/platform-functionality/status.html +34 -34
  445. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +34 -34
  446. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +34 -34
  447. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +34 -34
  448. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +34 -34
  449. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +34 -34
  450. nautobot/tenancy/api/views.py +2 -1
  451. nautobot/users/tests/test_api.py +2 -2
  452. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
  453. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
  454. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
  455. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
  456. nautobot/virtualization/urls.py +3 -61
  457. nautobot/virtualization/views.py +48 -72
  458. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/METADATA +27 -27
  459. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/RECORD +463 -439
  460. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/LICENSE.txt +0 -0
  461. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/NOTICE +0 -0
  462. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/WHEEL +0 -0
  463. {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/entry_points.txt +0 -0
nautobot/extras/views.py CHANGED
@@ -566,132 +566,45 @@ class ObjectAssignContactOrTeamView(generic.ObjectEditView):
566
566
  #
567
567
 
568
568
 
569
- class CustomFieldListView(generic.ObjectListView):
569
+ class CustomFieldUIViewSet(NautobotUIViewSet):
570
+ bulk_update_form_class = forms.CustomFieldBulkEditForm
570
571
  queryset = CustomField.objects.all()
571
- table = tables.CustomFieldTable
572
- filterset = filters.CustomFieldFilterSet
573
- filterset_form = forms.CustomFieldFilterForm
572
+ serializer_class = serializers.CustomFieldSerializer
573
+ filterset_class = filters.CustomFieldFilterSet
574
+ filterset_form_class = forms.CustomFieldFilterForm
575
+ form_class = forms.CustomFieldForm
576
+ table_class = tables.CustomFieldTable
577
+ template_name = "extras/customfield_update.html"
574
578
  action_buttons = ("add",)
575
579
 
576
-
577
- class CustomFieldView(generic.ObjectView):
578
- queryset = CustomField.objects.all()
579
-
580
-
581
- class CustomFieldEditView(generic.ObjectEditView):
582
- queryset = CustomField.objects.all()
583
- model_form = forms.CustomFieldForm
584
- template_name = "extras/customfield_edit.html"
585
-
586
580
  def get_extra_context(self, request, instance):
587
- ctx = super().get_extra_context(request, instance)
588
-
589
- if request.POST:
590
- ctx["choices"] = forms.CustomFieldChoiceFormSet(data=request.POST, instance=instance)
591
- else:
592
- ctx["choices"] = forms.CustomFieldChoiceFormSet(instance=instance)
593
-
594
- return ctx
595
-
596
- def post(self, request, *args, **kwargs):
597
- obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
598
- form = self.model_form(data=request.POST, files=request.FILES, instance=obj)
599
- restrict_form_fields(form, request.user)
600
-
601
- if form.is_valid():
602
- logger.debug("Form validation was successful")
603
-
604
- try:
605
- with transaction.atomic():
606
- object_created = not form.instance.present_in_database
607
- obj = form.save()
608
-
609
- # Check that the new object conforms with any assigned object-level permissions
610
- self.queryset.get(pk=obj.pk)
611
-
612
- # ---> BEGIN difference from ObjectEditView.post()
613
- # Process the formsets for choices
614
- ctx = self.get_extra_context(request, obj)
615
- choices = ctx["choices"]
616
- if choices.is_valid():
617
- choices.save()
618
- else:
619
- raise RuntimeError(choices.errors)
620
- # <--- END difference from ObjectEditView.post()
621
- verb = "Created" if object_created else "Modified"
622
- msg = f"{verb} {self.queryset.model._meta.verbose_name}"
623
- logger.info(f"{msg} {obj} (PK: {obj.pk})")
624
- try:
625
- msg = format_html('{} <a href="{}">{}</a>', msg, obj.get_absolute_url(), obj)
626
- except AttributeError:
627
- msg = format_html("{} {}", msg, obj)
628
- messages.success(request, msg)
629
-
630
- if "_addanother" in request.POST:
631
- # If the object has clone_fields, pre-populate a new instance of the form
632
- if hasattr(obj, "clone_fields"):
633
- url = f"{request.path}?{prepare_cloned_fields(obj)}"
634
- return redirect(url)
581
+ context = super().get_extra_context(request, instance)
635
582
 
636
- return redirect(request.get_full_path())
583
+ if self.action in ("create", "update"):
584
+ if request.POST:
585
+ context["choices"] = forms.CustomFieldChoiceFormSet(data=request.POST, instance=instance)
586
+ else:
587
+ context["choices"] = forms.CustomFieldChoiceFormSet(instance=instance)
637
588
 
638
- return_url = form.cleaned_data.get("return_url")
639
- if url_has_allowed_host_and_scheme(url=return_url, allowed_hosts=request.get_host()):
640
- return redirect(iri_to_uri(return_url))
641
- else:
642
- return redirect(self.get_return_url(request, obj))
589
+ return context
643
590
 
644
- except ObjectDoesNotExist:
645
- msg = "Object save failed due to object-level permissions violation"
646
- logger.debug(msg)
647
- form.add_error(None, msg)
648
- # ---> BEGIN difference from ObjectEditView.post()
649
- except RuntimeError:
650
- msg = "Errors encountered when saving custom field choices. See below."
651
- logger.debug(msg)
652
- form.add_error(None, msg)
653
- except ProtectedError as err:
654
- # e.g. Trying to delete a choice that is in use.
655
- err_msg = err.args[0]
656
- protected_obj = err.protected_objects[0]
657
- msg = f"{protected_obj.value}: {err_msg} Please cancel this edit and start again."
658
- logger.debug(msg)
659
- form.add_error(None, msg)
660
- # <--- END difference from ObjectEditView.post()
591
+ def form_save(self, form, **kwargs):
592
+ obj = super().form_save(form, **kwargs)
661
593
 
594
+ # Process the formset for choices
595
+ ctx = self.get_extra_context(self.request, obj)
596
+ choices = ctx["choices"]
597
+ if choices.is_valid():
598
+ choices.save()
662
599
  else:
663
- logger.debug("Form validation failed")
664
-
665
- return render(
666
- request,
667
- self.template_name,
668
- {
669
- "obj": obj,
670
- "obj_type": self.queryset.model._meta.verbose_name,
671
- "form": form,
672
- "return_url": self.get_return_url(request, obj),
673
- "editing": obj.present_in_database,
674
- **self.get_extra_context(request, obj),
675
- },
676
- )
677
-
678
-
679
- class CustomFieldDeleteView(generic.ObjectDeleteView):
680
- queryset = CustomField.objects.all()
681
-
600
+ raise ValidationError(choices.errors)
682
601
 
683
- class CustomFieldBulkDeleteView(generic.BulkDeleteView):
684
- queryset = CustomField.objects.all()
685
- table = tables.CustomFieldTable
686
- filterset = filters.CustomFieldFilterSet
687
- form = forms.CustomFieldBulkDeleteForm
602
+ return obj
688
603
 
689
604
 
690
605
  #
691
606
  # Custom Links
692
607
  #
693
-
694
-
695
608
  class CustomLinkUIViewSet(NautobotUIViewSet):
696
609
  bulk_update_form_class = forms.CustomLinkBulkEditForm
697
610
  filterset_class = filters.CustomLinkFilterSet
@@ -1230,32 +1143,24 @@ class GitRepositoryResultView(generic.ObjectView):
1230
1143
  #
1231
1144
 
1232
1145
 
1233
- class GraphQLQueryListView(generic.ObjectListView):
1146
+ class GraphQLQueryUIViewSet(
1147
+ ObjectDetailViewMixin,
1148
+ ObjectListViewMixin,
1149
+ ObjectEditViewMixin,
1150
+ ObjectDestroyViewMixin,
1151
+ ObjectBulkDestroyViewMixin,
1152
+ ObjectChangeLogViewMixin,
1153
+ ObjectNotesViewMixin,
1154
+ ):
1155
+ filterset_form_class = forms.GraphQLQueryFilterForm
1234
1156
  queryset = GraphQLQuery.objects.all()
1235
- table = tables.GraphQLQueryTable
1236
- filterset = filters.GraphQLQueryFilterSet
1237
- filterset_form = forms.GraphQLQueryFilterForm
1157
+ form_class = forms.GraphQLQueryForm
1158
+ filterset_class = filters.GraphQLQueryFilterSet
1159
+ serializer_class = serializers.GraphQLQuerySerializer
1160
+ table_class = tables.GraphQLQueryTable
1238
1161
  action_buttons = ("add",)
1239
1162
 
1240
1163
 
1241
- class GraphQLQueryView(generic.ObjectView):
1242
- queryset = GraphQLQuery.objects.all()
1243
-
1244
-
1245
- class GraphQLQueryEditView(generic.ObjectEditView):
1246
- queryset = GraphQLQuery.objects.all()
1247
- model_form = forms.GraphQLQueryForm
1248
-
1249
-
1250
- class GraphQLQueryDeleteView(generic.ObjectDeleteView):
1251
- queryset = GraphQLQuery.objects.all()
1252
-
1253
-
1254
- class GraphQLQueryBulkDeleteView(generic.BulkDeleteView):
1255
- queryset = GraphQLQuery.objects.all()
1256
- table = tables.GraphQLQueryTable
1257
-
1258
-
1259
1164
  #
1260
1165
  # Image attachments
1261
1166
  #
@@ -2148,59 +2053,57 @@ class JobHookUIViewSet(NautobotUIViewSet):
2148
2053
  #
2149
2054
 
2150
2055
 
2151
- class JobResultListView(generic.ObjectListView):
2152
- """
2153
- List JobResults
2154
- """
2155
-
2156
- queryset = JobResult.objects.defer("result").select_related("job_model", "user")
2157
- filterset = filters.JobResultFilterSet
2158
- filterset_form = forms.JobResultFilterForm
2159
- table = tables.JobResultTable
2160
- action_buttons = ()
2161
-
2162
-
2163
- class JobResultDeleteView(generic.ObjectDeleteView):
2056
+ class JobResultUIViewSet(
2057
+ ObjectDetailViewMixin,
2058
+ ObjectListViewMixin,
2059
+ ObjectDestroyViewMixin,
2060
+ ObjectBulkDestroyViewMixin,
2061
+ ):
2062
+ filterset_class = filters.JobResultFilterSet
2063
+ filterset_form_class = forms.JobResultFilterForm
2064
+ serializer_class = serializers.JobResultSerializer
2065
+ table_class = tables.JobResultTable
2164
2066
  queryset = JobResult.objects.all()
2165
-
2166
-
2167
- class JobResultBulkDeleteView(generic.BulkDeleteView):
2168
- queryset = JobResult.objects.defer("result").select_related("job_model", "user")
2169
- table = tables.JobResultTable
2170
- filterset = filters.JobResultFilterSet
2171
-
2172
-
2173
- class JobResultView(generic.ObjectView):
2174
- """
2175
- Display a JobResult and its Job data.
2176
- """
2177
-
2178
- queryset = JobResult.objects.prefetch_related("job_model", "user")
2179
- template_name = "extras/jobresult.html"
2067
+ action_buttons = ()
2180
2068
 
2181
2069
  def get_extra_context(self, request, instance):
2182
- associated_record = None
2183
- job_class = None
2184
- if instance.job_model is not None:
2185
- job_class = instance.job_model.job_class
2070
+ context = super().get_extra_context(request, instance)
2071
+ if self.action == "retrieve":
2072
+ job_class = None
2073
+ if instance and instance.job_model:
2074
+ job_class = instance.job_model.job_class
2075
+
2076
+ context.update(
2077
+ {
2078
+ "job": job_class,
2079
+ "associated_record": None,
2080
+ "result": instance,
2081
+ }
2082
+ )
2186
2083
 
2187
- return {
2188
- "job": job_class,
2189
- "associated_record": associated_record,
2190
- "result": instance,
2191
- **super().get_extra_context(request, instance),
2192
- }
2084
+ return context
2193
2085
 
2086
+ def get_queryset(self):
2087
+ queryset = super().get_queryset().select_related("job_model", "user")
2194
2088
 
2195
- class JobLogEntryTableView(generic.GenericView):
2196
- """
2197
- Display a table of `JobLogEntry` objects for a given `JobResult` instance.
2198
- """
2089
+ if not self.detail:
2090
+ queryset = queryset.defer("result", "task_args", "task_kwargs", "celery_kwargs", "traceback", "meta")
2199
2091
 
2200
- queryset = JobResult.objects.all()
2092
+ return queryset
2093
+
2094
+ @action(
2095
+ detail=True,
2096
+ url_path="log-table",
2097
+ url_name="log-table",
2098
+ custom_view_base_action="view",
2099
+ )
2100
+ def log_table(self, request, pk=None):
2101
+ """
2102
+ Custom action to return a rendered JobLogEntry table for a JobResult.
2103
+ """
2201
2104
 
2202
- def get(self, request, pk=None):
2203
2105
  instance = get_object_or_404(self.queryset.restrict(request.user, "view"), pk=pk)
2106
+
2204
2107
  filter_q = request.GET.get("q")
2205
2108
  if filter_q:
2206
2109
  queryset = instance.job_log_entries.filter(
@@ -2208,14 +2111,15 @@ class JobLogEntryTableView(generic.GenericView):
2208
2111
  )
2209
2112
  else:
2210
2113
  queryset = instance.job_log_entries.all()
2114
+
2211
2115
  log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
2212
2116
  paginate = {
2213
2117
  "paginator_class": EnhancedPaginator,
2214
2118
  "per_page": get_paginate_count(request),
2215
2119
  }
2216
2120
  RequestConfig(request, paginate).configure(log_table)
2217
- table = log_table.as_html(request)
2218
- return HttpResponse(table)
2121
+
2122
+ return HttpResponse(log_table.as_html(request))
2219
2123
 
2220
2124
 
2221
2125
  #
@@ -2431,35 +2335,22 @@ class ObjectMetadataUIViewSet(
2431
2335
  #
2432
2336
 
2433
2337
 
2434
- class NoteView(generic.ObjectView):
2435
- queryset = Note.objects.all()
2436
-
2437
-
2438
- class NoteListView(generic.ObjectListView):
2439
- """
2440
- List Notes
2441
- """
2442
-
2338
+ class NoteUIViewSet(
2339
+ ObjectChangeLogViewMixin, ObjectDestroyViewMixin, ObjectDetailViewMixin, ObjectEditViewMixin, ObjectListViewMixin
2340
+ ):
2341
+ filterset_class = filters.NoteFilterSet
2342
+ filterset_form_class = forms.NoteFilterForm
2343
+ form_class = forms.NoteForm
2443
2344
  queryset = Note.objects.all()
2444
- filterset = filters.NoteFilterSet
2445
- filterset_form = forms.NoteFilterForm
2446
- table = tables.NoteTable
2345
+ serializer_class = serializers.NoteSerializer
2346
+ table_class = tables.NoteTable
2447
2347
  action_buttons = ()
2448
2348
 
2449
-
2450
- class NoteEditView(generic.ObjectEditView):
2451
- queryset = Note.objects.all()
2452
- model_form = forms.NoteForm
2453
-
2454
2349
  def alter_obj(self, obj, request, url_args, url_kwargs):
2455
2350
  obj.user = request.user
2456
2351
  return obj
2457
2352
 
2458
2353
 
2459
- class NoteDeleteView(generic.ObjectDeleteView):
2460
- queryset = Note.objects.all()
2461
-
2462
-
2463
2354
  class ObjectNotesView(generic.GenericView):
2464
2355
  """
2465
2356
  Present a list of notes associated to a particular object.
@@ -2745,123 +2636,37 @@ class SecretProviderParametersFormView(generic.GenericView):
2745
2636
  )
2746
2637
 
2747
2638
 
2748
- class SecretsGroupListView(generic.ObjectListView):
2639
+ class SecretsGroupUIViewSet(NautobotUIViewSet):
2640
+ bulk_update_form_class = forms.SecretsGroupBulkEditForm
2641
+ filterset_class = filters.SecretsGroupFilterSet
2642
+ filterset_form_class = forms.SecretsGroupFilterForm
2643
+ form_class = forms.SecretsGroupForm
2644
+ serializer_class = serializers.SecretsGroupSerializer
2645
+ table_class = tables.SecretsGroupTable
2646
+ template_name = "extras/secretsgroup_update.html"
2749
2647
  queryset = SecretsGroup.objects.all()
2750
- filterset = filters.SecretsGroupFilterSet
2751
- filterset_form = forms.SecretsGroupFilterForm
2752
- table = tables.SecretsGroupTable
2753
- action_buttons = ("add",)
2754
-
2755
-
2756
- class SecretsGroupView(generic.ObjectView):
2757
- queryset = SecretsGroup.objects.all()
2758
-
2759
- def get_extra_context(self, request, instance):
2760
- return {"secrets_group_associations": SecretsGroupAssociation.objects.filter(secrets_group=instance)}
2761
-
2762
-
2763
- class SecretsGroupEditView(generic.ObjectEditView):
2764
- queryset = SecretsGroup.objects.all()
2765
- model_form = forms.SecretsGroupForm
2766
- template_name = "extras/secretsgroup_edit.html"
2767
-
2768
- def get_extra_context(self, request, instance):
2769
- ctx = super().get_extra_context(request, instance)
2770
-
2771
- if request.POST:
2772
- ctx["secrets"] = forms.SecretsGroupAssociationFormSet(data=request.POST, instance=instance)
2773
- else:
2774
- ctx["secrets"] = forms.SecretsGroupAssociationFormSet(instance=instance)
2775
-
2776
- return ctx
2777
-
2778
- def post(self, request, *args, **kwargs):
2779
- obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
2780
- form = self.model_form(data=request.POST, files=request.FILES, instance=obj)
2781
- restrict_form_fields(form, request.user)
2782
-
2783
- if form.is_valid():
2784
- logger.debug("Form validation was successful")
2785
-
2786
- try:
2787
- with transaction.atomic():
2788
- object_created = not form.instance.present_in_database
2789
- obj = form.save()
2790
2648
 
2791
- # Check that the new object conforms with any assigned object-level permissions
2792
- self.queryset.get(pk=obj.pk)
2793
-
2794
- # Process the formsets for secrets
2795
- ctx = self.get_extra_context(request, obj)
2796
- secrets = ctx["secrets"]
2797
- if secrets.is_valid():
2798
- secrets.save()
2799
- else:
2800
- raise RuntimeError(secrets.errors)
2801
- verb = "Created" if object_created else "Modified"
2802
- msg = f"{verb} {self.queryset.model._meta.verbose_name}"
2803
- logger.info(f"{msg} {obj} (PK: {obj.pk})")
2804
- try:
2805
- msg = format_html('{} <a href="{}">{}</a>', msg, obj.get_absolute_url(), obj)
2806
- except AttributeError:
2807
- msg = format_html("{} {}", msg, obj)
2808
- messages.success(request, msg)
2809
-
2810
- if "_addanother" in request.POST:
2811
- # If the object has clone_fields, pre-populate a new instance of the form
2812
- if hasattr(obj, "clone_fields"):
2813
- url = f"{request.path}?{prepare_cloned_fields(obj)}"
2814
- return redirect(url)
2815
-
2816
- return redirect(request.get_full_path())
2817
-
2818
- return_url = form.cleaned_data.get("return_url")
2819
- if url_has_allowed_host_and_scheme(url=return_url, allowed_hosts=request.get_host()):
2820
- return redirect(iri_to_uri(return_url))
2821
- else:
2822
- return redirect(self.get_return_url(request, obj))
2649
+ def get_extra_context(self, request, instance=None):
2650
+ context = super().get_extra_context(request, instance)
2651
+ if self.action == "retrieve" and instance:
2652
+ context["secrets_group_associations"] = SecretsGroupAssociation.objects.filter(secrets_group=instance)
2653
+ if self.action in ("create", "update"):
2654
+ if request.method == "POST":
2655
+ context["secrets"] = forms.SecretsGroupAssociationFormSet(data=request.POST, instance=instance)
2656
+ else:
2657
+ context["secrets"] = forms.SecretsGroupAssociationFormSet(instance=instance)
2658
+ return context
2823
2659
 
2824
- except ObjectDoesNotExist:
2825
- msg = "Object save failed due to object-level permissions violation."
2826
- logger.debug(msg)
2827
- form.add_error(None, msg)
2828
- except RuntimeError:
2829
- msg = "Errors encountered when saving secrets group associations. See below."
2830
- logger.debug(msg)
2831
- form.add_error(None, msg)
2832
- except ProtectedError as err:
2833
- # e.g. Trying to delete a choice that is in use.
2834
- err_msg = err.args[0]
2835
- protected_obj = err.protected_objects[0]
2836
- msg = f"{protected_obj.value}: {err_msg} Please cancel this edit and start again."
2837
- logger.debug(msg)
2838
- form.add_error(None, msg)
2660
+ def form_save(self, form, **kwargs):
2661
+ obj = super().form_save(form, **kwargs)
2662
+ secrets = forms.SecretsGroupAssociationFormSet(data=self.request.POST, instance=form.instance)
2839
2663
 
2664
+ if secrets.is_valid():
2665
+ secrets.save()
2840
2666
  else:
2841
- logger.debug("Form validation failed")
2667
+ raise ValidationError(secrets.errors)
2842
2668
 
2843
- return render(
2844
- request,
2845
- self.template_name,
2846
- {
2847
- "obj": obj,
2848
- "obj_type": self.queryset.model._meta.verbose_name,
2849
- "form": form,
2850
- "return_url": self.get_return_url(request, obj),
2851
- "editing": obj.present_in_database,
2852
- **self.get_extra_context(request, obj),
2853
- },
2854
- )
2855
-
2856
-
2857
- class SecretsGroupDeleteView(generic.ObjectDeleteView):
2858
- queryset = SecretsGroup.objects.all()
2859
-
2860
-
2861
- class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
2862
- queryset = SecretsGroup.objects.all()
2863
- filterset = filters.SecretsGroupFilterSet
2864
- table = tables.SecretsGroupTable
2669
+ return obj
2865
2670
 
2866
2671
 
2867
2672
  #
@@ -1,7 +1,8 @@
1
1
  from django.conf import settings
2
2
  from django.core.cache import cache
3
3
  from django.shortcuts import get_object_or_404
4
- from drf_spectacular.utils import extend_schema, extend_schema_view
4
+ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
5
+ import netaddr
5
6
  from rest_framework import status
6
7
  from rest_framework.decorators import action
7
8
  from rest_framework.exceptions import APIException
@@ -123,6 +124,26 @@ class PrefixViewSet(NautobotModelViewSet):
123
124
  return serializers.PrefixLegacySerializer
124
125
  return super().get_serializer_class()
125
126
 
127
+ @staticmethod
128
+ def get_ipaddress_param(request, name, default):
129
+ """Extract IP address parameter from request.
130
+ :param request: django-rest request object
131
+ :param name: name of the query parameter which contains the IP address string
132
+ :param default: fallback IP address string in case no value is present in the query parameter
133
+ :return: tuple of mutually exclusive (Response|None, netaddr.IPAddress object|None).
134
+ Will return a Response in case the client sent incorrectly formatted IP Address in
135
+ the parameter. It is up to the caller to return the Response.
136
+ """
137
+ response, result = None, None
138
+ try:
139
+ result = netaddr.IPAddress(request.query_params.get(name, default))
140
+ except (netaddr.core.AddrFormatError, ValueError, TypeError) as e:
141
+ response = Response(
142
+ {"detail": (f"Incorrectly formatted address in parameter {name}: {e}")},
143
+ status=status.HTTP_400_BAD_REQUEST,
144
+ )
145
+ return response, result
146
+
126
147
  class LocationIncompatibleLegacyBehavior(APIException):
127
148
  status_code = 412
128
149
  default_detail = (
@@ -225,6 +246,33 @@ class PrefixViewSet(NautobotModelViewSet):
225
246
 
226
247
  return Response(serializer.data)
227
248
 
249
+ @extend_schema(
250
+ methods=["get", "post"],
251
+ parameters=[
252
+ OpenApiParameter(
253
+ name="range_start",
254
+ location="query",
255
+ description="IP from which enumeration/allocation should start.",
256
+ type={
257
+ "oneOf": [
258
+ {"type": "string", "format": "ipv6"},
259
+ {"type": "string", "format": "ipv4"},
260
+ ]
261
+ },
262
+ ),
263
+ OpenApiParameter(
264
+ name="range_end",
265
+ location="query",
266
+ description="IP from which enumeration/allocation should stop.",
267
+ type={
268
+ "oneOf": [
269
+ {"type": "string", "format": "ipv6"},
270
+ {"type": "string", "format": "ipv4"},
271
+ ]
272
+ },
273
+ ),
274
+ ],
275
+ )
228
276
  @extend_schema(methods=["get"], responses={200: serializers.AvailableIPSerializer(many=True)})
229
277
  @extend_schema(
230
278
  methods=["post"],
@@ -251,6 +299,21 @@ class PrefixViewSet(NautobotModelViewSet):
251
299
  """
252
300
  prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
253
301
 
302
+ default_first, default_last = netaddr.IPAddress(prefix.prefix.first), netaddr.IPAddress(prefix.prefix.last)
303
+ ((error_response_start, range_start), (error_response_end, range_end)) = (
304
+ self.get_ipaddress_param(request, "range_start", default_first),
305
+ self.get_ipaddress_param(request, "range_end", default_last),
306
+ )
307
+ if response := error_response_start or error_response_end:
308
+ return response
309
+
310
+ available_ips = prefix.get_available_ips()
311
+ # range_start and range_end are inclusive
312
+ if range_start > default_first:
313
+ available_ips.remove(netaddr.IPRange(default_first, range_start - 1))
314
+ if range_end < default_last:
315
+ available_ips.remove(netaddr.IPRange(range_end + 1, default_last))
316
+
254
317
  # Create the next available IP within the prefix
255
318
  if request.method == "POST":
256
319
  with cache.lock(
@@ -260,23 +323,23 @@ class PrefixViewSet(NautobotModelViewSet):
260
323
  requested_ips = request.data if isinstance(request.data, list) else [request.data]
261
324
 
262
325
  # Determine if the requested number of IPs is available
263
- available_ips = prefix.get_available_ips()
264
326
  if available_ips.size < len(requested_ips):
265
327
  return Response(
266
328
  {
267
329
  "detail": (
268
330
  f"An insufficient number of IP addresses are available within the prefix {prefix} "
269
- f"({len(requested_ips)} requested, {len(available_ips)} available)"
331
+ f"({len(requested_ips)} requested, {available_ips.size} available between "
332
+ f"{range_start} and {range_end})."
270
333
  )
271
334
  },
272
335
  status=status.HTTP_204_NO_CONTENT,
273
336
  )
274
337
 
275
338
  # Assign addresses from the list of available IPs and copy Namespace assignment from the parent Prefix
276
- available_ips = iter(available_ips)
277
339
  prefix_length = prefix.prefix.prefixlen
340
+ available_ips_iter = iter(available_ips)
278
341
  for requested_ip in requested_ips:
279
- requested_ip["address"] = f"{next(available_ips)}/{prefix_length}"
342
+ requested_ip["address"] = f"{next(available_ips_iter)}/{prefix_length}"
280
343
  requested_ip["namespace"] = prefix.namespace
281
344
 
282
345
  # Initialize the serializer with a list or a single object depending on what was requested
@@ -307,7 +370,7 @@ class PrefixViewSet(NautobotModelViewSet):
307
370
 
308
371
  # Calculate available IPs within the prefix
309
372
  ip_list = []
310
- for index, ip in enumerate(prefix.get_available_ips(), start=1):
373
+ for index, ip in enumerate(available_ips, start=1):
311
374
  ip_list.append(ip)
312
375
  if index == limit:
313
376
  break