nautobot 2.4.14__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 (433) 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/management/commands/check_job_approval_status.py +47 -0
  7. nautobot/core/management/commands/generate_test_data.py +1 -1
  8. nautobot/core/management/commands/migrate.py +1 -1
  9. nautobot/core/models/tree_queries.py +17 -0
  10. nautobot/core/settings.py +2 -2
  11. nautobot/core/tables.py +25 -2
  12. nautobot/core/templates/base_django.html +1 -1
  13. nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
  14. nautobot/core/templates/generic/object_list.html +17 -20
  15. nautobot/core/templates/inc/breadcrumbs.html +14 -0
  16. nautobot/core/templatetags/buttons.py +2 -4
  17. nautobot/core/templatetags/helpers.py +29 -6
  18. nautobot/core/templatetags/ui_framework.py +21 -0
  19. nautobot/core/testing/filters.py +20 -3
  20. nautobot/core/testing/forms.py +1 -1
  21. nautobot/core/tests/integration/test_filters.py +2 -2
  22. nautobot/core/tests/test_breadcrumbs.py +366 -0
  23. nautobot/core/tests/test_commands.py +40 -0
  24. nautobot/core/tests/test_filters.py +51 -1
  25. nautobot/core/tests/test_forms.py +1 -1
  26. nautobot/core/tests/test_graphql.py +4 -4
  27. nautobot/core/tests/test_titles.py +183 -0
  28. nautobot/core/tests/test_tree_queries.py +30 -0
  29. nautobot/core/tests/test_views.py +2 -2
  30. nautobot/core/tests/test_views_utils.py +1 -1
  31. nautobot/core/ui/breadcrumbs.py +538 -0
  32. nautobot/core/ui/bulk_buttons.py +53 -0
  33. nautobot/core/ui/object_detail.py +31 -8
  34. nautobot/core/ui/titles.py +127 -0
  35. nautobot/core/ui/utils.py +25 -0
  36. nautobot/core/utils/migrations.py +1 -1
  37. nautobot/core/views/__init__.py +1 -1
  38. nautobot/core/views/mixins.py +26 -1
  39. nautobot/core/views/renderers.py +20 -2
  40. nautobot/core/views/utils.py +13 -12
  41. nautobot/dcim/api/serializers.py +9 -0
  42. nautobot/dcim/choices.py +53 -0
  43. nautobot/dcim/filters/__init__.py +15 -3
  44. nautobot/dcim/forms.py +120 -7
  45. nautobot/dcim/management/commands/trace_paths.py +1 -1
  46. nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
  47. nautobot/dcim/models/device_component_templates.py +8 -0
  48. nautobot/dcim/models/device_components.py +31 -12
  49. nautobot/dcim/models/devices.py +1 -1
  50. nautobot/dcim/models/power.py +171 -10
  51. nautobot/dcim/models/racks.py +7 -4
  52. nautobot/dcim/tables/devices.py +2 -0
  53. nautobot/dcim/tables/devicetypes.py +1 -0
  54. nautobot/dcim/tables/power.py +30 -2
  55. nautobot/dcim/templates/dcim/device.html +2 -2
  56. nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
  57. nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
  58. nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
  59. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
  60. nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
  61. nautobot/dcim/tests/test_api.py +24 -4
  62. nautobot/dcim/tests/test_filters.py +91 -13
  63. nautobot/dcim/tests/test_models.py +262 -0
  64. nautobot/dcim/tests/test_views.py +20 -12
  65. nautobot/dcim/utils.py +9 -0
  66. nautobot/dcim/views.py +390 -77
  67. nautobot/extras/factory.py +19 -20
  68. nautobot/extras/filters/__init__.py +3 -2
  69. nautobot/extras/filters/mixins.py +15 -1
  70. nautobot/extras/forms/__init__.py +2 -1
  71. nautobot/extras/forms/forms.py +62 -0
  72. nautobot/extras/managers.py +4 -1
  73. nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
  74. nautobot/extras/models/customfields.py +1 -2
  75. nautobot/extras/models/datasources.py +1 -2
  76. nautobot/extras/models/jobs.py +7 -3
  77. nautobot/extras/plugins/views.py +24 -1
  78. nautobot/extras/secrets/__init__.py +1 -1
  79. nautobot/extras/tables.py +9 -0
  80. nautobot/extras/templates/extras/customfield.html +2 -129
  81. nautobot/extras/templates/extras/customfield_edit.html +2 -108
  82. nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
  83. nautobot/extras/templates/extras/customfield_update.html +108 -0
  84. nautobot/extras/templates/extras/inc/jobresult.html +7 -3
  85. nautobot/extras/templates/extras/jobresult.html +2 -155
  86. nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
  87. nautobot/extras/templates/extras/marketplace.html +5 -6
  88. nautobot/extras/templates/extras/note.html +2 -53
  89. nautobot/extras/templates/extras/note_retrieve.html +53 -0
  90. nautobot/extras/templates/extras/plugins_list.html +5 -6
  91. nautobot/extras/templatetags/custom_links.py +2 -2
  92. nautobot/extras/templatetags/job_buttons.py +1 -1
  93. nautobot/extras/templatetags/plugins.py +1 -1
  94. nautobot/extras/tests/integration/test_computedfields.py +2 -2
  95. nautobot/extras/tests/integration/test_customfields.py +14 -11
  96. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  97. nautobot/extras/tests/integration/test_notes.py +1 -1
  98. nautobot/extras/tests/integration/test_plugins.py +6 -6
  99. nautobot/extras/tests/integration/test_relationships.py +2 -2
  100. nautobot/extras/tests/test_filters.py +9 -0
  101. nautobot/extras/tests/test_forms.py +2 -2
  102. nautobot/extras/tests/test_plugins.py +2 -3
  103. nautobot/extras/tests/test_relationships.py +7 -7
  104. nautobot/extras/tests/test_views.py +172 -1
  105. nautobot/extras/urls.py +3 -59
  106. nautobot/extras/utils.py +1 -1
  107. nautobot/extras/views.py +77 -178
  108. nautobot/ipam/tables.py +8 -15
  109. nautobot/ipam/tests/migration/test_migrations.py +8 -8
  110. nautobot/ipam/tests/test_api.py +2 -2
  111. nautobot/ipam/tests/test_models.py +1 -1
  112. nautobot/project-static/docs/404.html +23 -0
  113. nautobot/project-static/docs/apps/index.html +23 -0
  114. nautobot/project-static/docs/apps/nautobot-apps.html +23 -0
  115. nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
  116. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +28 -0
  117. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +25 -0
  118. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +128 -20
  119. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +37 -4
  120. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +39 -6
  121. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +25 -0
  122. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -0
  123. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +32 -5
  124. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +41 -8
  125. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +39 -7
  126. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +43 -10
  127. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +74 -59
  128. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +143 -28
  129. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +43 -12
  130. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +135 -53
  131. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +229 -36
  132. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +27 -1
  133. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -1
  134. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +162 -18
  135. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +258 -51
  136. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2620
  137. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +25 -0
  138. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +154 -55
  139. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +150 -35
  140. nautobot/project-static/docs/development/apps/api/configuration-view.html +23 -0
  141. nautobot/project-static/docs/development/apps/api/database-backend-config.html +23 -0
  142. nautobot/project-static/docs/development/apps/api/models/django-admin.html +23 -0
  143. nautobot/project-static/docs/development/apps/api/models/global-search.html +23 -0
  144. nautobot/project-static/docs/development/apps/api/models/graphql.html +23 -0
  145. nautobot/project-static/docs/development/apps/api/models/index.html +23 -0
  146. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +23 -0
  147. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +23 -0
  148. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +23 -0
  149. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +23 -0
  150. nautobot/project-static/docs/development/apps/api/platform-features/index.html +23 -0
  151. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +23 -0
  152. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +23 -0
  153. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +23 -0
  154. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +23 -0
  155. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +23 -0
  156. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +23 -0
  157. nautobot/project-static/docs/development/apps/api/prometheus.html +23 -0
  158. nautobot/project-static/docs/development/apps/api/setup.html +23 -0
  159. nautobot/project-static/docs/development/apps/api/testing.html +23 -0
  160. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +23 -0
  161. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +23 -0
  162. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +23 -0
  163. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +23 -0
  164. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +23 -0
  165. nautobot/project-static/docs/development/apps/api/views/base-template.html +23 -0
  166. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +23 -0
  167. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +23 -0
  168. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +23 -0
  169. nautobot/project-static/docs/development/apps/api/views/index.html +23 -0
  170. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +23 -0
  171. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -2
  172. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +23 -0
  173. nautobot/project-static/docs/development/apps/api/views/notes.html +23 -0
  174. nautobot/project-static/docs/development/apps/api/views/rest-api.html +23 -0
  175. nautobot/project-static/docs/development/apps/api/views/urls.html +23 -0
  176. nautobot/project-static/docs/development/apps/index.html +23 -0
  177. nautobot/project-static/docs/development/apps/migration/code-updates.html +23 -0
  178. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +23 -0
  179. nautobot/project-static/docs/development/apps/migration/from-v1.html +23 -0
  180. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +23 -0
  181. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +23 -0
  182. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +23 -0
  183. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +23 -0
  184. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +26 -3
  185. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
  186. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +23 -0
  187. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +23 -0
  188. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +23 -0
  189. nautobot/project-static/docs/development/apps/porting-from-netbox.html +26 -3
  190. nautobot/project-static/docs/development/core/application-registry.html +23 -0
  191. nautobot/project-static/docs/development/core/best-practices.html +23 -0
  192. nautobot/project-static/docs/development/core/bootstrap-ui.html +23 -0
  193. nautobot/project-static/docs/development/core/caching.html +23 -0
  194. nautobot/project-static/docs/development/core/controllers.html +23 -0
  195. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +23 -0
  196. nautobot/project-static/docs/development/core/generic-views.html +23 -0
  197. nautobot/project-static/docs/development/core/getting-started.html +23 -0
  198. nautobot/project-static/docs/development/core/homepage.html +23 -0
  199. nautobot/project-static/docs/development/core/index.html +23 -0
  200. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +23 -0
  201. nautobot/project-static/docs/development/core/model-checklist.html +23 -0
  202. nautobot/project-static/docs/development/core/model-features.html +23 -0
  203. nautobot/project-static/docs/development/core/natural-keys.html +23 -0
  204. nautobot/project-static/docs/development/core/navigation-menu.html +23 -0
  205. nautobot/project-static/docs/development/core/release-checklist.html +23 -0
  206. nautobot/project-static/docs/development/core/role-internals.html +23 -0
  207. nautobot/project-static/docs/development/core/settings.html +23 -0
  208. nautobot/project-static/docs/development/core/style-guide.html +23 -0
  209. nautobot/project-static/docs/development/core/templates.html +23 -0
  210. nautobot/project-static/docs/development/core/testing.html +23 -0
  211. nautobot/project-static/docs/development/core/ui-component-framework.html +713 -255
  212. nautobot/project-static/docs/development/core/user-preferences.html +23 -0
  213. nautobot/project-static/docs/development/index.html +23 -0
  214. nautobot/project-static/docs/development/jobs/getting-started.html +23 -0
  215. nautobot/project-static/docs/development/jobs/index.html +23 -0
  216. nautobot/project-static/docs/development/jobs/installation.html +23 -0
  217. nautobot/project-static/docs/development/jobs/job-extensions.html +23 -0
  218. nautobot/project-static/docs/development/jobs/job-logging.html +23 -0
  219. nautobot/project-static/docs/development/jobs/job-patterns.html +23 -0
  220. nautobot/project-static/docs/development/jobs/job-structure.html +23 -0
  221. nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -0
  222. nautobot/project-static/docs/development/jobs/testing.html +23 -0
  223. nautobot/project-static/docs/index.html +23 -0
  224. nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
  225. nautobot/project-static/docs/media/power_distribution.png +0 -0
  226. nautobot/project-static/docs/objects.inv +0 -0
  227. nautobot/project-static/docs/overview/application_stack.html +23 -0
  228. nautobot/project-static/docs/overview/design_philosophy.html +23 -0
  229. nautobot/project-static/docs/release-notes/index.html +23 -0
  230. nautobot/project-static/docs/release-notes/version-1.0.html +23 -0
  231. nautobot/project-static/docs/release-notes/version-1.1.html +23 -0
  232. nautobot/project-static/docs/release-notes/version-1.2.html +23 -0
  233. nautobot/project-static/docs/release-notes/version-1.3.html +23 -0
  234. nautobot/project-static/docs/release-notes/version-1.4.html +23 -0
  235. nautobot/project-static/docs/release-notes/version-1.5.html +23 -0
  236. nautobot/project-static/docs/release-notes/version-1.6.html +23 -0
  237. nautobot/project-static/docs/release-notes/version-2.0.html +23 -0
  238. nautobot/project-static/docs/release-notes/version-2.1.html +23 -0
  239. nautobot/project-static/docs/release-notes/version-2.2.html +23 -0
  240. nautobot/project-static/docs/release-notes/version-2.3.html +23 -0
  241. nautobot/project-static/docs/release-notes/version-2.4.html +231 -0
  242. nautobot/project-static/docs/requirements.txt +2 -2
  243. nautobot/project-static/docs/search/search_index.json +1 -1
  244. nautobot/project-static/docs/sitemap.xml +303 -299
  245. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  246. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +23 -0
  247. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +23 -0
  248. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +23 -0
  249. nautobot/project-static/docs/user-guide/administration/configuration/index.html +23 -0
  250. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +23 -0
  251. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +23 -0
  252. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +23 -0
  253. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +23 -0
  254. nautobot/project-static/docs/user-guide/administration/guides/docker.html +23 -0
  255. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +23 -0
  256. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +23 -0
  257. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +23 -0
  258. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +23 -0
  259. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +23 -0
  260. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +23 -0
  261. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +23 -0
  262. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +23 -0
  263. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +23 -0
  264. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +23 -0
  265. nautobot/project-static/docs/user-guide/administration/installation/index.html +23 -0
  266. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +23 -0
  267. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +23 -0
  268. nautobot/project-static/docs/user-guide/administration/installation/services.html +23 -0
  269. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +23 -0
  270. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +23 -0
  271. nautobot/project-static/docs/user-guide/administration/security/index.html +23 -0
  272. nautobot/project-static/docs/user-guide/administration/security/notices.html +23 -0
  273. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +284 -219
  274. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +23 -0
  275. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +23 -0
  276. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +23 -0
  277. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +23 -0
  278. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +23 -0
  279. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +23 -0
  280. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +23 -0
  281. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +23 -0
  282. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +23 -0
  283. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +23 -0
  284. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +23 -0
  285. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +23 -0
  286. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +23 -0
  287. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +23 -0
  288. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +23 -0
  289. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +23 -0
  290. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +23 -0
  291. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +23 -0
  292. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +23 -0
  293. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +23 -0
  294. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +23 -0
  295. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +23 -0
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +23 -0
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +23 -0
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +23 -0
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +23 -0
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +23 -0
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +23 -0
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +23 -0
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +23 -0
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +23 -0
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +23 -0
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +23 -0
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +23 -0
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +23 -0
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +23 -0
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +23 -0
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +23 -0
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +23 -0
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +23 -0
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +23 -0
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +23 -0
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +23 -0
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +23 -0
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +23 -0
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +23 -0
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +23 -0
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +23 -0
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +23 -0
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +23 -0
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +305 -5
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -1
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +23 -0
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +136 -3
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +41 -1
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +40 -0
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +23 -0
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +23 -0
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +23 -0
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +23 -0
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +23 -0
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +23 -0
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +23 -0
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +23 -0
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +23 -0
  339. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +23 -0
  340. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +23 -0
  341. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +23 -0
  342. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +23 -0
  343. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +23 -0
  344. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +23 -0
  345. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +23 -0
  346. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +23 -0
  347. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +23 -0
  348. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +23 -0
  349. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +23 -0
  350. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +23 -0
  351. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +23 -0
  352. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +23 -0
  353. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +23 -0
  354. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +23 -0
  355. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +23 -0
  356. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +23 -0
  357. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +23 -0
  358. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +23 -0
  359. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +23 -0
  360. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +23 -0
  361. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +23 -0
  362. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +23 -0
  363. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +23 -0
  364. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +23 -0
  365. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +23 -0
  366. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +23 -0
  367. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +23 -0
  368. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +23 -0
  369. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +23 -0
  370. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +23 -0
  371. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +23 -0
  372. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +23 -0
  373. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +23 -0
  374. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +23 -0
  375. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +23 -0
  376. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +23 -0
  377. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +23 -0
  378. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +23 -0
  379. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +23 -0
  380. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +23 -0
  381. nautobot/project-static/docs/user-guide/index.html +23 -0
  382. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +23 -0
  383. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +23 -0
  384. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +23 -0
  385. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +23 -0
  386. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +23 -0
  387. nautobot/project-static/docs/user-guide/platform-functionality/events.html +23 -0
  388. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +23 -0
  389. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +23 -0
  390. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +23 -0
  391. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +23 -0
  392. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +23 -0
  393. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +23 -0
  394. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +23 -0
  395. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -1
  396. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +23 -0
  397. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +23 -0
  398. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +23 -0
  399. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +23 -0
  400. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +23 -0
  401. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +23 -0
  402. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +23 -0
  403. nautobot/project-static/docs/user-guide/platform-functionality/note.html +23 -0
  404. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +23 -0
  405. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +23 -0
  406. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +23 -0
  407. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +23 -0
  408. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +23 -0
  409. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +23 -0
  410. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +23 -0
  411. nautobot/project-static/docs/user-guide/platform-functionality/role.html +23 -0
  412. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +23 -0
  413. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +23 -0
  414. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +23 -0
  415. nautobot/project-static/docs/user-guide/platform-functionality/status.html +23 -0
  416. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +23 -0
  417. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +23 -0
  418. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +23 -0
  419. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +23 -0
  420. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +23 -0
  421. nautobot/users/tests/test_api.py +2 -2
  422. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
  423. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
  424. nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
  425. nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
  426. nautobot/virtualization/urls.py +3 -61
  427. nautobot/virtualization/views.py +48 -72
  428. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/METADATA +24 -24
  429. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/RECORD +433 -416
  430. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/LICENSE.txt +0 -0
  431. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/NOTICE +0 -0
  432. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/WHEEL +0 -0
  433. {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/entry_points.txt +0 -0
@@ -6,4 +6,4 @@
6
6
  {% if object.rack %}
7
7
  <li>{{ object.rack|hyperlinked_object }}</li>
8
8
  {% endif %}
9
- {% endblock extra_breadcrumbs %}
9
+ {% endblock extra_breadcrumbs %}
@@ -1,7 +1,13 @@
1
1
  import uuid
2
2
 
3
+ from django.urls import reverse
4
+ from selenium.webdriver.common.by import By
5
+ from selenium.webdriver.support import expected_conditions
6
+ from selenium.webdriver.support.ui import WebDriverWait
7
+
3
8
  from nautobot.core.testing.integration import (
4
9
  BulkOperationsTestCases,
10
+ SeleniumTestCase,
5
11
  )
6
12
  from nautobot.dcim.models import Device
7
13
  from nautobot.extras.tests.integration import create_test_device
@@ -28,3 +34,58 @@ class DeviceBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCas
28
34
  create_test_device("Test Device Integration Test 3", test_uuid=test_uuid)
29
35
  create_test_device("Test Device Integration Test 4", "Test Location 2", test_uuid=test_uuid)
30
36
  create_test_device("Test Device Integration Test 5", "Test Location 2", test_uuid=test_uuid)
37
+
38
+
39
+ class DeviceBulkUrlParamTestCase(SeleniumTestCase):
40
+ """
41
+ Integration test to check that when a bulk edit is initiated from a filtered device list view it does not fill in bulk edit form.
42
+ """
43
+
44
+ def setUp(self):
45
+ super().setUp()
46
+ self.user.is_superuser = True
47
+ self.user.save()
48
+ self.login(self.user.username, self.password)
49
+
50
+ self.device1 = create_test_device("Device 1")
51
+
52
+ def tearDown(self):
53
+ self.logout()
54
+ super().tearDown()
55
+
56
+ def test_param_fills_device_type(self):
57
+ """
58
+ This test:
59
+ 1 Go to device list page with param for device_type
60
+ 2 Selects the row checkbox for the device with that device_type
61
+ 3 Submits the bulk edit form
62
+ 4 Checks that the device_type field is blank (i.e. "---------") on the bulk edit form
63
+ """
64
+
65
+ # Go to Device list page
66
+ self.browser.visit(
67
+ self.live_server_url + reverse("dcim:device_list") + f"?device_type={self.device1.device_type.pk}"
68
+ )
69
+
70
+ # 2) Check the row checkbox exactly matching the PK
71
+ pk = str(self.device1.pk)
72
+ cb_xpath = f'//input[@type="checkbox" and @name="pk" and @value="{pk}"]'
73
+ checkbox = WebDriverWait(self.browser.driver, 2).until(
74
+ expected_conditions.element_to_be_clickable((By.XPATH, cb_xpath))
75
+ )
76
+ self.browser.driver.execute_script("arguments[0].click();", checkbox)
77
+
78
+ # Click the bulk-edit button (it uses formaction)
79
+ bulk_url = reverse("dcim:device_bulk_edit")
80
+ btn_xpath = f'//button[@type="submit" and @formaction="{bulk_url}"]'
81
+ bulk_btn = WebDriverWait(self.browser.driver, 2).until(
82
+ expected_conditions.element_to_be_clickable((By.XPATH, btn_xpath))
83
+ )
84
+ # We know this works since if nothing is selected, you will be redirected back to the list view with a message
85
+ bulk_btn.click()
86
+
87
+ self.assertTrue(
88
+ WebDriverWait(self.browser.driver, 2).until(
89
+ lambda d: d.find_element(By.CLASS_NAME, "select2-selection__placeholder").text.strip() == "---------"
90
+ )
91
+ )
@@ -16,8 +16,10 @@ from nautobot.dcim.choices import (
16
16
  InterfaceModeChoices,
17
17
  InterfaceTypeChoices,
18
18
  PortTypeChoices,
19
+ PowerFeedBreakerPoleChoices,
19
20
  PowerFeedTypeChoices,
20
21
  PowerOutletTypeChoices,
22
+ PowerPanelTypeChoices,
21
23
  PowerPortTypeChoices,
22
24
  SoftwareImageFileHashingAlgorithmChoices,
23
25
  SubdeviceRoleChoices,
@@ -3064,7 +3066,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
3064
3066
  # Interface name starts with parent device's position in VC; e.g. 1/1, 1/2, 1/3...
3065
3067
  Interface.objects.create(
3066
3068
  device=device,
3067
- name=f"{i%3+1}/{j}",
3069
+ name=f"{i % 3 + 1}/{j}",
3068
3070
  type=InterfaceTypeChoices.TYPE_1GE_FIXED,
3069
3071
  status=interface_status,
3070
3072
  role=interface_role,
@@ -3166,6 +3168,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
3166
3168
 
3167
3169
  class PowerPanelTest(APIViewTestCases.APIViewTestCase):
3168
3170
  model = PowerPanel
3171
+ choices_fields = ["panel_type", "power_path"]
3169
3172
 
3170
3173
  @classmethod
3171
3174
  def setUpTestData(cls):
@@ -3204,7 +3207,7 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
3204
3207
 
3205
3208
  class PowerFeedTest(APIViewTestCases.APIViewTestCase):
3206
3209
  model = PowerFeed
3207
- choices_fields = ["phase", "supply", "type"]
3210
+ choices_fields = ["phase", "supply", "type", "breaker_pole_count", "power_path"]
3208
3211
 
3209
3212
  @classmethod
3210
3213
  def setUpTestData(cls):
@@ -3229,8 +3232,20 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
3229
3232
  )
3230
3233
 
3231
3234
  power_panels = (
3232
- PowerPanel.objects.create(location=location, rack_group=rackgroup, name="Power Panel 1"),
3233
- PowerPanel.objects.create(location=location, rack_group=rackgroup, name="Power Panel 2"),
3235
+ PowerPanel.objects.create(
3236
+ location=location,
3237
+ rack_group=rackgroup,
3238
+ name="Power Panel 1",
3239
+ panel_type=PowerPanelTypeChoices.TYPE_UTILITY,
3240
+ breaker_position_count=42,
3241
+ ),
3242
+ PowerPanel.objects.create(
3243
+ location=location,
3244
+ rack_group=rackgroup,
3245
+ name="Power Panel 2",
3246
+ panel_type=PowerPanelTypeChoices.TYPE_RPP,
3247
+ breaker_position_count=24,
3248
+ ),
3234
3249
  )
3235
3250
 
3236
3251
  PRIMARY = PowerFeedTypeChoices.TYPE_PRIMARY
@@ -3285,6 +3300,9 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
3285
3300
  {
3286
3301
  "name": "Power Feed 4A",
3287
3302
  "power_panel": power_panels[0].pk,
3303
+ "destination_panel": power_panels[1].pk,
3304
+ "breaker_position": 5,
3305
+ "breaker_pole_count": PowerFeedBreakerPoleChoices.POLE_1,
3288
3306
  "rack": racks[3].pk,
3289
3307
  "status": statuses[0].pk,
3290
3308
  "type": PRIMARY,
@@ -3292,6 +3310,8 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
3292
3310
  {
3293
3311
  "name": "Power Feed 4B",
3294
3312
  "power_panel": power_panels[1].pk,
3313
+ "breaker_position": 10,
3314
+ "breaker_pole_count": PowerFeedBreakerPoleChoices.POLE_2,
3295
3315
  "rack": racks[3].pk,
3296
3316
  "status": statuses[0].pk,
3297
3317
  "type": REDUNDANT,
@@ -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()