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
@@ -14,10 +14,12 @@ from nautobot.dcim.choices import (
14
14
  InterfaceModeChoices,
15
15
  InterfaceTypeChoices,
16
16
  PortTypeChoices,
17
+ PowerFeedBreakerPoleChoices,
17
18
  PowerFeedPhaseChoices,
18
19
  PowerFeedSupplyChoices,
19
20
  PowerFeedTypeChoices,
20
21
  PowerOutletFeedLegChoices,
22
+ PowerPathChoices,
21
23
  RackDimensionUnitChoices,
22
24
  RackTypeChoices,
23
25
  RackWidthChoices,
@@ -120,6 +122,7 @@ from nautobot.dcim.models import (
120
122
  VirtualChassis,
121
123
  VirtualDeviceContext,
122
124
  )
125
+ from nautobot.extras.filters.mixins import RoleFilter, StatusFilter
123
126
  from nautobot.extras.models import ExternalIntegration, Role, SecretsGroup, Status, Tag
124
127
  from nautobot.ipam.models import IPAddress, Namespace, Prefix, Service, VLAN, VLANGroup
125
128
  from nautobot.tenancy.models import Tenant
@@ -230,6 +233,7 @@ def common_test_data(cls):
230
233
  PowerPanel.objects.create(name="Power Panel 1", location=loc0, rack_group=rack_groups[0]),
231
234
  PowerPanel.objects.create(name="Power Panel 2", location=loc1, rack_group=rack_groups[1]),
232
235
  PowerPanel.objects.create(name="Power Panel 3", location=loc1, rack_group=rack_groups[2]),
236
+ PowerPanel.objects.create(name="Power Panel 4", location=loc0),
233
237
  )
234
238
  power_panels[0].tags.set(Tag.objects.get_for_model(PowerPanel))
235
239
  power_panels[1].tags.set(Tag.objects.get_for_model(PowerPanel)[:3])
@@ -350,7 +354,19 @@ def common_test_data(cls):
350
354
  power_feeds = (
351
355
  PowerFeed.objects.create(name="Power Feed 1", rack=racks[0], power_panel=power_panels[0], status=pf_status),
352
356
  PowerFeed.objects.create(name="Power Feed 2", rack=racks[1], power_panel=power_panels[1], status=pf_status),
353
- PowerFeed.objects.create(name="Power Feed 3", rack=racks[2], power_panel=power_panels[2], status=pf_status),
357
+ PowerFeed.objects.create(
358
+ name="Power Feed 3",
359
+ rack=racks[2],
360
+ power_panel=power_panels[2],
361
+ status=pf_status,
362
+ destination_panel=power_panels[0],
363
+ ),
364
+ PowerFeed.objects.create(
365
+ name="Power Feed 4",
366
+ power_panel=power_panels[1],
367
+ status=pf_status,
368
+ destination_panel=power_panels[3],
369
+ ),
354
370
  )
355
371
  power_feeds[0].tags.set(Tag.objects.get_for_model(PowerFeed))
356
372
  power_feeds[1].tags.set(Tag.objects.get_for_model(PowerFeed)[:3])
@@ -696,42 +712,42 @@ def common_test_data(cls):
696
712
  # Create 3 of each component template on the first two module types
697
713
  for i in range(6):
698
714
  ConsolePortTemplate.objects.create(
699
- name=f"Test Filters Module Console Port {i+1}",
715
+ name=f"Test Filters Module Console Port {i + 1}",
700
716
  module_type=module_types[i % 2],
701
717
  )
702
718
  ConsoleServerPortTemplate.objects.create(
703
- name=f"Test Filters Module Console Server Port {i+1}",
719
+ name=f"Test Filters Module Console Server Port {i + 1}",
704
720
  module_type=module_types[i % 2],
705
721
  )
706
722
  ppt = PowerPortTemplate.objects.create(
707
- name=f"Test Filters Module Power Port {i+1}",
723
+ name=f"Test Filters Module Power Port {i + 1}",
708
724
  module_type=module_types[i % 2],
709
725
  )
710
726
  PowerOutletTemplate.objects.create(
711
- name=f"Test Filters Module Power Outlet {i+1}",
727
+ name=f"Test Filters Module Power Outlet {i + 1}",
712
728
  power_port_template=ppt,
713
729
  module_type=module_types[i % 2],
714
730
  )
715
731
  InterfaceTemplate.objects.create(
716
- name=f"Test Filters Module Interface {i+1}",
732
+ name=f"Test Filters Module Interface {i + 1}",
717
733
  type=InterfaceTypeChoices.TYPE_1GE_FIXED,
718
734
  module_type=module_types[i % 2],
719
735
  )
720
736
  rpt = RearPortTemplate.objects.create(
721
- name=f"Test Filters Module Rear Port {i+1}",
737
+ name=f"Test Filters Module Rear Port {i + 1}",
722
738
  module_type=module_types[i % 2],
723
739
  type=PortTypeChoices.TYPE_8P8C,
724
740
  positions=10,
725
741
  )
726
742
  FrontPortTemplate.objects.create(
727
- name=f"Test Filters Module Front Port {i+1}",
743
+ name=f"Test Filters Module Front Port {i + 1}",
728
744
  module_type=module_types[i % 2],
729
745
  rear_port_template=rpt,
730
746
  rear_port_position=i + 1,
731
747
  type=PortTypeChoices.TYPE_8P8C,
732
748
  )
733
749
  ModuleBayTemplate.objects.create(
734
- name=f"Test Filters Module Module Bay {i+1}",
750
+ name=f"Test Filters Module Module Bay {i + 1}",
735
751
  position=i + 1,
736
752
  module_type=module_types[i % 2],
737
753
  requires_first_party_modules=(i % 2 == 0), # True for even indices, False for odd
@@ -1253,7 +1269,7 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
1253
1269
 
1254
1270
  rack_group = RackGroup.objects.get(name="Rack Group 3")
1255
1271
  tenant = Tenant.objects.filter(tenant_group__isnull=False).first()
1256
- rack_role = Role.objects.get_for_model(Rack).first()
1272
+ cls.rack_role = Role.objects.get_for_model(Rack).first()
1257
1273
 
1258
1274
  Rack.objects.create(
1259
1275
  name="Rack 4",
@@ -1262,7 +1278,7 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
1262
1278
  rack_group=rack_group,
1263
1279
  tenant=tenant,
1264
1280
  status=cls.rack_statuses[0],
1265
- role=rack_role,
1281
+ role=cls.rack_role,
1266
1282
  serial="ABCDEF",
1267
1283
  asset_tag="1004",
1268
1284
  type=RackTypeChoices.TYPE_2POST,
@@ -1291,6 +1307,34 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
1291
1307
  params = {"outer_unit": [RackDimensionUnitChoices.UNIT_MILLIMETER]}
1292
1308
  self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1293
1309
 
1310
+ def test_role_status_negation(self):
1311
+ """https://github.com/nautobot/nautobot/issues/6456"""
1312
+ self.assertIsInstance(self.filterset().filters["role"], RoleFilter)
1313
+ self.assertIsInstance(self.filterset().filters["role__n"], RoleFilter)
1314
+ with self.subTest("Negated role (id)"):
1315
+ params = {"role__n": [self.rack_role.pk]}
1316
+ self.assertQuerysetEqualAndNotEmpty(
1317
+ self.filterset(params, self.queryset).qs, Rack.objects.exclude(role=self.rack_role)
1318
+ )
1319
+ with self.subTest("Negated role (name)"):
1320
+ params = {"role__n": [self.rack_role.name]}
1321
+ self.assertQuerysetEqualAndNotEmpty(
1322
+ self.filterset(params, self.queryset).qs, Rack.objects.exclude(role=self.rack_role)
1323
+ )
1324
+
1325
+ self.assertIsInstance(self.filterset().filters["status"], StatusFilter)
1326
+ self.assertIsInstance(self.filterset().filters["status__n"], StatusFilter)
1327
+ with self.subTest("Negated status (id)"):
1328
+ params = {"status__n": [self.rack_statuses[0].pk]}
1329
+ self.assertQuerysetEqualAndNotEmpty(
1330
+ self.filterset(params, self.queryset).qs, Rack.objects.exclude(status=self.rack_statuses[0])
1331
+ )
1332
+ with self.subTest("Negated status (name)"):
1333
+ params = {"status__n": [self.rack_statuses[0].name]}
1334
+ self.assertQuerysetEqualAndNotEmpty(
1335
+ self.filterset(params, self.queryset).qs, Rack.objects.exclude(status=self.rack_statuses[0])
1336
+ )
1337
+
1294
1338
 
1295
1339
  class RackReservationTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
1296
1340
  queryset = RackReservation.objects.all()
@@ -3559,8 +3603,6 @@ class PowerPanelTestCase(FilterTestCases.FilterTestCase):
3559
3603
  def setUpTestData(cls):
3560
3604
  common_test_data(cls)
3561
3605
 
3562
- PowerPanel.objects.create(name="Power Panel 4", location=cls.loc1)
3563
-
3564
3606
 
3565
3607
  class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCase):
3566
3608
  queryset = PowerFeed.objects.all()
@@ -3568,8 +3610,12 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3568
3610
  generic_filter_tests = [
3569
3611
  ("amperage",),
3570
3612
  ("available_power",),
3613
+ ("breaker_pole_count",),
3614
+ ("breaker_position",),
3571
3615
  ("cable", "cable__id"),
3572
3616
  ("comments",),
3617
+ ("destination_panel", "destination_panel__id"),
3618
+ ("destination_panel", "destination_panel__name"),
3573
3619
  ("max_utilization",),
3574
3620
  ("name",),
3575
3621
  ("power_panel", "power_panel__id"),
@@ -3589,6 +3635,7 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3589
3635
  PowerFeed.objects.get(name="Power Feed 1"),
3590
3636
  PowerFeed.objects.get(name="Power Feed 2"),
3591
3637
  PowerFeed.objects.get(name="Power Feed 3"),
3638
+ PowerFeed.objects.get(name="Power Feed 4"),
3592
3639
  )
3593
3640
 
3594
3641
  pf_statuses = Status.objects.get_for_model(PowerFeed)
@@ -3602,6 +3649,9 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3602
3649
  amperage=100,
3603
3650
  max_utilization=10,
3604
3651
  comments="PFA",
3652
+ power_path=PowerPathChoices.PATH_A,
3653
+ breaker_position=1,
3654
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
3605
3655
  )
3606
3656
  PowerFeed.objects.filter(pk=power_feeds[1].pk).update(
3607
3657
  status=pf_statuses[1],
@@ -3612,6 +3662,9 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3612
3662
  amperage=200,
3613
3663
  max_utilization=20,
3614
3664
  comments="PFB",
3665
+ power_path=PowerPathChoices.PATH_B,
3666
+ breaker_position=4,
3667
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
3615
3668
  )
3616
3669
  PowerFeed.objects.filter(pk=power_feeds[2].pk).update(
3617
3670
  status=pf_statuses[2],
@@ -3622,14 +3675,32 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3622
3675
  amperage=300,
3623
3676
  max_utilization=30,
3624
3677
  comments="PFC",
3678
+ power_path=PowerPathChoices.PATH_A,
3679
+ breaker_position=9,
3680
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_3,
3681
+ )
3682
+ PowerFeed.objects.filter(pk=power_feeds[3].pk).update(
3683
+ status=pf_statuses[0],
3684
+ type=PowerFeedTypeChoices.TYPE_REDUNDANT,
3685
+ supply=PowerFeedSupplyChoices.SUPPLY_AC,
3686
+ phase=PowerFeedPhaseChoices.PHASE_3PHASE,
3687
+ voltage=400,
3688
+ amperage=400,
3689
+ max_utilization=40,
3690
+ comments="PFD",
3691
+ power_path=PowerPathChoices.PATH_B,
3692
+ breaker_position=15,
3693
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
3625
3694
  )
3626
3695
 
3627
3696
  power_feeds[0].refresh_from_db()
3628
3697
  power_feeds[1].refresh_from_db()
3629
3698
  power_feeds[2].refresh_from_db()
3699
+ power_feeds[3].refresh_from_db()
3630
3700
  power_feeds[0].validated_save()
3631
3701
  power_feeds[1].validated_save()
3632
3702
  power_feeds[2].validated_save()
3703
+ power_feeds[3].validated_save()
3633
3704
 
3634
3705
  power_ports = (
3635
3706
  PowerPort.objects.get(name="Power Port 1"),
@@ -3674,6 +3745,13 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
3674
3745
  self.queryset.filter(phase=PowerFeedPhaseChoices.PHASE_3PHASE),
3675
3746
  )
3676
3747
 
3748
+ def test_power_path(self):
3749
+ params = {"power_path": [PowerPathChoices.PATH_A]}
3750
+ self.assertQuerysetEqualAndNotEmpty(
3751
+ self.filterset(params, self.queryset).qs,
3752
+ self.queryset.filter(power_path=PowerPathChoices.PATH_A),
3753
+ )
3754
+
3677
3755
 
3678
3756
  class DeviceRedundancyGroupTestCase(FilterTestCases.FilterTestCase):
3679
3757
  queryset = DeviceRedundancyGroup.objects.all()
@@ -19,6 +19,10 @@ from nautobot.dcim.choices import (
19
19
  InterfaceModeChoices,
20
20
  InterfaceTypeChoices,
21
21
  PortTypeChoices,
22
+ PowerFeedBreakerPoleChoices,
23
+ PowerFeedPhaseChoices,
24
+ PowerFeedSupplyChoices,
25
+ PowerFeedTypeChoices,
22
26
  PowerOutletFeedLegChoices,
23
27
  PowerOutletTypeChoices,
24
28
  PowerPortTypeChoices,
@@ -53,6 +57,7 @@ from nautobot.dcim.models import (
53
57
  ModuleFamily,
54
58
  ModuleType,
55
59
  Platform,
60
+ PowerFeed,
56
61
  PowerOutlet,
57
62
  PowerOutletTemplate,
58
63
  PowerPanel,
@@ -2389,6 +2394,263 @@ class CableTestCase(ModelTestCases.BaseModelTestCase):
2389
2394
  self.device1.delete()
2390
2395
 
2391
2396
 
2397
+ class PowerFeedTestCase(ModelTestCases.BaseModelTestCase):
2398
+ model = PowerFeed
2399
+
2400
+ @classmethod
2401
+ def setUpTestData(cls):
2402
+ cls.location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
2403
+ cls.status = Status.objects.get_for_model(PowerFeed).first()
2404
+ cls.rack_status = Status.objects.get_for_model(Rack).first()
2405
+
2406
+ cls.source_panel = PowerPanel.objects.create(location=cls.location, name="Source Panel 1")
2407
+ cls.destination_panel = PowerPanel.objects.create(location=cls.location, name="Destination Panel 1")
2408
+
2409
+ # Create location in different hierarchy for rack validation test
2410
+ cls.other_location = Location.objects.create(
2411
+ name="Other Location",
2412
+ location_type=LocationType.objects.get(name="Campus"),
2413
+ status=Status.objects.get_for_model(Location).first(),
2414
+ )
2415
+
2416
+ cls.rack = Rack.objects.create(location=cls.location, name="Test Rack", status=cls.rack_status)
2417
+ cls.other_rack = Rack.objects.create(location=cls.other_location, name="Other Rack", status=cls.rack_status)
2418
+
2419
+ PowerFeed.objects.create(
2420
+ name="Test Power Feed 1",
2421
+ power_panel=cls.source_panel,
2422
+ rack=cls.rack,
2423
+ status=cls.status,
2424
+ )
2425
+ PowerFeed.objects.create(
2426
+ name="Test Power Feed 2",
2427
+ power_panel=cls.source_panel,
2428
+ status=cls.status,
2429
+ )
2430
+ PowerFeed.objects.create(
2431
+ name="Test Power Feed 3",
2432
+ power_panel=cls.destination_panel,
2433
+ status=cls.status,
2434
+ )
2435
+
2436
+ def test_destination_panel_self_reference_validation(self):
2437
+ """Test that a power feed cannot reference itself."""
2438
+ feed = PowerFeed(
2439
+ name="Self Reference",
2440
+ power_panel=self.source_panel,
2441
+ destination_panel=self.source_panel,
2442
+ status=self.status,
2443
+ )
2444
+
2445
+ with self.assertRaises(ValidationError) as cm:
2446
+ feed.full_clean()
2447
+ self.assertIn("destination_panel", cm.exception.message_dict)
2448
+
2449
+ def test_circuit_breaker_position_conflict_validation(self):
2450
+ """Test that overlapping circuit breaker positions raise validation errors."""
2451
+ # Create first feed at position 1 with 2 poles (occupies 1,3)
2452
+ PowerFeed.objects.create(
2453
+ name="Feed 1",
2454
+ power_panel=self.source_panel,
2455
+ status=self.status,
2456
+ breaker_position=1,
2457
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
2458
+ )
2459
+
2460
+ # Try to create conflicting feed at position 3
2461
+ conflicting_feed = PowerFeed(
2462
+ name="Feed 2",
2463
+ power_panel=self.source_panel,
2464
+ status=self.status,
2465
+ breaker_position=3,
2466
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
2467
+ )
2468
+
2469
+ with self.assertRaises(ValidationError) as cm:
2470
+ conflicting_feed.full_clean()
2471
+ self.assertIn("breaker_position", cm.exception.message_dict)
2472
+
2473
+ def test_rack_location_hierarchy_validation(self):
2474
+ """Test that rack must belong to same location hierarchy as power panel."""
2475
+ feed = PowerFeed(
2476
+ name="Invalid Rack Location",
2477
+ power_panel=self.source_panel,
2478
+ rack=self.other_rack, # Different location hierarchy
2479
+ status=self.status,
2480
+ )
2481
+
2482
+ with self.assertRaises(ValidationError) as cm:
2483
+ feed.full_clean()
2484
+ self.assertIn("rack", cm.exception.message_dict)
2485
+
2486
+ def test_ac_voltage_negative_validation(self):
2487
+ """Test that AC supply cannot have negative voltage."""
2488
+ feed = PowerFeed(
2489
+ name="Negative Voltage",
2490
+ power_panel=self.source_panel,
2491
+ status=self.status,
2492
+ voltage=-120,
2493
+ supply=PowerFeedSupplyChoices.SUPPLY_AC,
2494
+ )
2495
+
2496
+ with self.assertRaises(ValidationError) as cm:
2497
+ feed.full_clean()
2498
+ self.assertIn("voltage", cm.exception.message_dict)
2499
+
2500
+ def test_phase_designation_single_pole(self):
2501
+ """Test phase designation calculation for single-pole breakers."""
2502
+ # Pattern: positions 1,2=A, 3,4=B, 5,6=C, 7,8=A, etc.
2503
+ test_cases = [
2504
+ (1, "A"),
2505
+ (2, "A"),
2506
+ (3, "B"),
2507
+ (4, "B"),
2508
+ (5, "C"),
2509
+ (6, "C"),
2510
+ (7, "A"),
2511
+ (8, "A"),
2512
+ (9, "B"),
2513
+ (10, "B"),
2514
+ ]
2515
+
2516
+ for position, expected_phase in test_cases:
2517
+ with self.subTest(position=position):
2518
+ feed = PowerFeed.objects.create(
2519
+ name=f"1P Test {position}",
2520
+ power_panel=self.source_panel,
2521
+ status=self.status,
2522
+ breaker_position=position,
2523
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
2524
+ # phase defaults to PHASE_SINGLE which is correct for all single-pole feeds
2525
+ )
2526
+ self.assertEqual(feed.phase_designation, expected_phase)
2527
+
2528
+ def test_phase_designation_two_pole_single_phase(self):
2529
+ """Test phase designation for 2-pole breakers delivering single-phase power."""
2530
+ # Common datacenter scenario: 2P breaker for 208V single-phase to rack PDU
2531
+ # Uses two phase conductors but delivers single-phase power
2532
+ test_cases = [
2533
+ (1, "A-B"), # occupies 1,3 → A,B → 208V single-phase
2534
+ (2, "A-B"), # occupies 2,4 → A,B → 208V single-phase
2535
+ (3, "B-C"), # occupies 3,5 → B,C → 208V single-phase
2536
+ (4, "B-C"), # occupies 4,6 → B,C → 208V single-phase
2537
+ (5, "A-C"), # occupies 5,7 → C,A → sorted to A-C → 208V single-phase
2538
+ (6, "A-C"), # occupies 6,8 → C,A → sorted to A-C → 208V single-phase
2539
+ (7, "A-B"), # occupies 7,9 → A,B → 208V single-phase (cycle continues)
2540
+ ]
2541
+
2542
+ for position, expected_designation in test_cases:
2543
+ with self.subTest(position=position):
2544
+ feed = PowerFeed.objects.create(
2545
+ name=f"2P Single-Phase Test {position}",
2546
+ power_panel=self.source_panel,
2547
+ status=self.status,
2548
+ breaker_position=position,
2549
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
2550
+ # phase defaults to PHASE_SINGLE - correct for 208V single-phase feeds
2551
+ )
2552
+ self.assertEqual(feed.phase_designation, expected_designation)
2553
+ # Verify it's still marked as single-phase power
2554
+ self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_SINGLE)
2555
+
2556
+ def test_phase_designation_three_pole_three_phase(self):
2557
+ """Test phase designation for 3-pole breakers delivering three-phase power."""
2558
+ # True three-phase power using all three phases
2559
+ test_cases = [1, 2, 3, 4, 5]
2560
+
2561
+ for position in test_cases:
2562
+ with self.subTest(position=position):
2563
+ feed = PowerFeed.objects.create(
2564
+ name=f"3P Three-Phase Test {position}",
2565
+ power_panel=self.source_panel,
2566
+ status=self.status,
2567
+ breaker_position=position,
2568
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_3,
2569
+ phase=PowerFeedPhaseChoices.PHASE_3PHASE, # Explicitly set to three-phase
2570
+ )
2571
+ self.assertEqual(feed.phase_designation, "A-B-C")
2572
+ # Verify it's marked as three-phase power
2573
+ self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_3PHASE)
2574
+
2575
+ def test_phase_designation_edge_cases(self):
2576
+ """Test phase designation calculation for edge cases and None values."""
2577
+ # Test missing breaker_position
2578
+ feed = PowerFeed.objects.create(
2579
+ name="No Position",
2580
+ power_panel=self.source_panel,
2581
+ status=self.status,
2582
+ breaker_position=None,
2583
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
2584
+ )
2585
+ self.assertIsNone(feed.phase_designation)
2586
+
2587
+ # Test missing breaker_pole_count (should default to single pole)
2588
+ feed = PowerFeed.objects.create(
2589
+ name="No Pole Count",
2590
+ power_panel=self.source_panel,
2591
+ status=self.status,
2592
+ breaker_position=1,
2593
+ breaker_pole_count=None,
2594
+ )
2595
+ # Should default to single pole and have phase designation
2596
+ self.assertEqual(feed.breaker_pole_count, PowerFeedBreakerPoleChoices.POLE_1)
2597
+ self.assertEqual(feed.phase_designation, "A")
2598
+
2599
+ # Test both missing
2600
+ feed = PowerFeed.objects.create(
2601
+ name="No Position or Pole Count",
2602
+ power_panel=self.source_panel,
2603
+ status=self.status,
2604
+ breaker_position=None,
2605
+ breaker_pole_count=None,
2606
+ )
2607
+ self.assertIsNone(feed.phase_designation)
2608
+
2609
+ def test_phase_field_defaults(self):
2610
+ """Test that phase field defaults correctly and type field defaults to primary."""
2611
+ feed = PowerFeed.objects.create(
2612
+ name="Default Fields Test",
2613
+ power_panel=self.source_panel,
2614
+ status=self.status,
2615
+ )
2616
+
2617
+ # Verify defaults
2618
+ self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_SINGLE)
2619
+ self.assertEqual(feed.type, PowerFeedTypeChoices.TYPE_PRIMARY)
2620
+
2621
+ def test_occupied_positions(self):
2622
+ """Test occupied positions calculation."""
2623
+ feed = PowerFeed.objects.create(
2624
+ name="Test Feed",
2625
+ power_panel=self.source_panel,
2626
+ status=self.status,
2627
+ breaker_position=5,
2628
+ breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
2629
+ )
2630
+
2631
+ self.assertEqual(feed.get_occupied_positions(), {5, 7})
2632
+ self.assertEqual(feed.occupied_positions, "5, 7")
2633
+
2634
+ def test_breaker_pole_count_enforcement_in_save(self):
2635
+ """Test that breaker_pole_count defaults to POLE_1 when breaker_position is set during save."""
2636
+ # Create feed with breaker_position but no breaker_pole_count
2637
+ feed = PowerFeed(
2638
+ name="Test Save Enforcement",
2639
+ power_panel=self.source_panel,
2640
+ status=self.status,
2641
+ breaker_position=10,
2642
+ )
2643
+
2644
+ # Verify breaker_pole_count is None before save
2645
+ self.assertIsNone(feed.breaker_pole_count)
2646
+
2647
+ # Save without calling clean() to bypass form validation
2648
+ feed.save()
2649
+
2650
+ # Verify breaker_pole_count was set to POLE_1 during save
2651
+ self.assertEqual(feed.breaker_pole_count, PowerFeedBreakerPoleChoices.POLE_1)
2652
+
2653
+
2392
2654
  class PowerPanelTestCase(TestCase): # TODO: change to BaseModelTestCase once we have a PowerPanelFactory
2393
2655
  def test_power_panel_validation(self):
2394
2656
  status = Status.objects.get_for_model(Location).first()
@@ -715,32 +715,33 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
715
715
  # Test the view
716
716
  response = self.client.get(reverse("dcim:rack", args=[self.racks[0].pk]))
717
717
  self.assertHttpStatus(response, 200)
718
+
718
719
  # Validate Power Utilization for PowerFeed 11 is displaying correctly on Rack View.
719
720
  power_feed_11_html = """
720
- <td><div title="Used: 1200&#13;Count: 3840" class="progress text-center">
721
+ <td><div title="Used: 1263&#13;Count: 3840" class="progress text-center">
721
722
  <div class="progress-bar progress-bar-success"
722
- role="progressbar" aria-valuenow="31" aria-valuemin="0" aria-valuemax="100" style="width: 31%">
723
- 31%
723
+ role="progressbar" aria-valuenow="32" aria-valuemin="0" aria-valuemax="100" style="width: 32%">
724
+ 32%
724
725
  </div>
725
726
  </div></td>
726
727
  """
727
728
  self.assertContains(response, power_feed_11_html, html=True)
728
729
  # Validate Power Utilization for PowerFeed12 is displaying correctly on Rack View.
729
730
  power_feed_12_html = """
730
- <td><div title="Used: 2400&#13;Count: 3840" class="progress text-center">
731
+ <td><div title="Used: 2526&#13;Count: 3840" class="progress text-center">
731
732
  <div class="progress-bar progress-bar-success"
732
- role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100" style="width: 62%">
733
- 62%
733
+ role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" style="width: 65%">
734
+ 65%
734
735
  </div>
735
736
  </div></td>
736
737
  """
737
738
  self.assertContains(response, power_feed_12_html, html=True)
738
739
  # Validate Rack Power Utilization for Combined powerfeeds is displaying correctly on the Rack View
739
740
  total_utilization_html = """
740
- <td><div title="Used: 3600&#13;Count: 7680" class="progress text-center">
741
+ <td><div title="Used: 3789&#13;Count: 7680" class="progress text-center">
741
742
  <div class="progress-bar progress-bar-success"
742
- role="progressbar" aria-valuenow="46" aria-valuemin="0" aria-valuemax="100" style="width: 46%">
743
- 46%
743
+ role="progressbar" aria-valuenow="49" aria-valuemin="0" aria-valuemax="100" style="width: 49%">
744
+ 49%
744
745
  </div>
745
746
  </div></td>
746
747
  """
@@ -1656,6 +1657,7 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
1656
1657
  "type": PowerPortTypeChoices.TYPE_IEC_C14,
1657
1658
  "maximum_draw": 100,
1658
1659
  "allocated_draw": 50,
1660
+ "power_factor": Decimal("0.95"),
1659
1661
  }
1660
1662
 
1661
1663
  cls.bulk_create_data = {
@@ -1665,6 +1667,7 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
1665
1667
  "type": PowerPortTypeChoices.TYPE_IEC_C14,
1666
1668
  "maximum_draw": 100,
1667
1669
  "allocated_draw": 50,
1670
+ "power_factor": Decimal("0.95"),
1668
1671
  }
1669
1672
 
1670
1673
  cls.bulk_edit_data = {
@@ -1678,6 +1681,7 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
1678
1681
  "name": test_instance.name,
1679
1682
  "device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
1680
1683
  "module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
1684
+ "power_factor": Decimal("0.95"),
1681
1685
  "label": "new test label",
1682
1686
  "description": "new test description",
1683
1687
  }
@@ -2134,8 +2138,9 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2134
2138
  devicetypes[0].software_image_files.set(software_image_files[:2])
2135
2139
  devicetypes[1].software_image_files.set(software_image_files[2:])
2136
2140
  # Only valid software image files are those that belong to the device type or default images
2137
- valid_software_image_files = software_image_files[2:] + [
2138
- SoftwareImageFile.objects.filter(default_image=True).first()
2141
+ valid_software_image_files = [
2142
+ *software_image_files[2:],
2143
+ SoftwareImageFile.objects.filter(default_image=True).first(),
2139
2144
  ]
2140
2145
 
2141
2146
  cls.custom_fields = (
@@ -2885,16 +2890,18 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
2885
2890
  "type": PowerPortTypeChoices.TYPE_IEC_C14,
2886
2891
  "maximum_draw": 100,
2887
2892
  "allocated_draw": 50,
2893
+ "power_factor": Decimal("0.95"),
2888
2894
  "description": "A power port",
2889
2895
  "tags": [t.pk for t in Tag.objects.get_for_model(PowerPort)],
2890
2896
  }
2891
2897
 
2892
2898
  cls.bulk_create_data = {
2893
2899
  "device": device.pk,
2894
- "name_pattern": "Power Port [4-6]]",
2900
+ "name_pattern": "Power Port [4-6]",
2895
2901
  "type": PowerPortTypeChoices.TYPE_IEC_C14,
2896
2902
  "maximum_draw": 100,
2897
2903
  "allocated_draw": 50,
2904
+ "power_factor": Decimal("0.95"),
2898
2905
  "description": "A power port",
2899
2906
  "tags": [t.pk for t in Tag.objects.get_for_model(PowerPort)],
2900
2907
  }
@@ -2911,6 +2918,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
2911
2918
  "name": test_instance.name,
2912
2919
  "device": getattr(getattr(test_instance, "device", None), "pk", None),
2913
2920
  "module": getattr(getattr(test_instance, "module", None), "pk", None),
2921
+ "power_factor": Decimal("0.95"),
2914
2922
  "label": "new test label",
2915
2923
  "description": "new test description",
2916
2924
  }