nautobot 2.4.11__py3-none-any.whl → 2.4.13__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 (364) hide show
  1. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -114
  2. nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +1 -1
  3. nautobot/circuits/views.py +76 -6
  4. nautobot/cloud/navigation.py +1 -1
  5. nautobot/core/jobs/cleanup.py +4 -4
  6. nautobot/core/templates/components/panel/header_extra_content_table.html +12 -1
  7. nautobot/core/templates/components/panel/panel.html +4 -0
  8. nautobot/core/templates/generic/object_retrieve.html +1 -0
  9. nautobot/core/templates/inc/footer.html +4 -4
  10. nautobot/core/tests/test_utils.py +1 -177
  11. nautobot/core/tests/test_views.py +2 -2
  12. nautobot/core/ui/object_detail.py +1 -1
  13. nautobot/core/utils/module_loading.py +58 -63
  14. nautobot/dcim/filters/__init__.py +6 -0
  15. nautobot/dcim/filters/mixins.py +2 -1
  16. nautobot/dcim/migrations/0071_alter_consoleport_options_and_more.py +42 -0
  17. nautobot/dcim/models/device_components.py +10 -2
  18. nautobot/dcim/models/devices.py +28 -0
  19. nautobot/dcim/tables/devices.py +2 -0
  20. nautobot/dcim/templates/dcim/controller/base.html +1 -9
  21. nautobot/dcim/templates/dcim/controller_retrieve.html +2 -83
  22. nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +2 -25
  23. nautobot/dcim/templates/dcim/device.html +1 -34
  24. nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -1
  25. nautobot/dcim/templates/dcim/virtualchassis.html +2 -51
  26. nautobot/dcim/templates/dcim/virtualchassis_add.html +2 -22
  27. nautobot/dcim/templates/dcim/virtualchassis_create.html +22 -0
  28. nautobot/dcim/templates/dcim/virtualchassis_edit.html +2 -93
  29. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +51 -0
  30. nautobot/dcim/templates/dcim/virtualchassis_update.html +93 -0
  31. nautobot/dcim/tests/test_models.py +1 -0
  32. nautobot/dcim/urls.py +2 -59
  33. nautobot/dcim/views.py +180 -191
  34. nautobot/extras/datasources/git.py +2 -2
  35. nautobot/extras/jobs_ui.py +208 -0
  36. nautobot/extras/models/tags.py +4 -0
  37. nautobot/extras/tables.py +1 -1
  38. nautobot/extras/templates/extras/configcontextschema_retrieve.html +2 -2
  39. nautobot/extras/templates/extras/inc/configcontext_data.html +1 -1
  40. nautobot/extras/templates/extras/inc/configcontext_format.html +6 -0
  41. nautobot/extras/templates/extras/inc/json_data.html +1 -1
  42. nautobot/extras/templates/extras/inc/json_format.html +2 -2
  43. nautobot/extras/templates/extras/job_detail.html +1 -326
  44. nautobot/extras/templates/extras/tag_retrieve.html +1 -51
  45. nautobot/extras/views.py +83 -29
  46. nautobot/ipam/migrations/0052_alter_ipaddress_index_together_and_more.py +28 -0
  47. nautobot/ipam/models.py +13 -1
  48. nautobot/ipam/tests/test_api.py +1 -3
  49. nautobot/ipam/utils/testing.py +76 -29
  50. nautobot/project-static/docs/404.html +2 -2
  51. nautobot/project-static/docs/apps/index.html +2 -2
  52. nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
  53. nautobot/project-static/docs/assets/javascripts/{bundle.13a4f30d.min.js → bundle.56ea9cef.min.js} +2 -2
  54. nautobot/project-static/docs/assets/javascripts/{bundle.13a4f30d.min.js.map → bundle.56ea9cef.min.js.map} +2 -2
  55. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
  56. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
  57. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
  58. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
  59. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
  60. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
  61. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
  62. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
  63. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +2 -2
  64. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
  65. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
  66. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
  67. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
  68. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
  69. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
  70. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
  71. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
  72. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
  73. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
  74. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
  75. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
  76. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
  77. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2 -2
  78. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +4 -2
  79. nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
  80. nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
  81. nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
  82. nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
  83. nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
  84. nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
  85. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
  86. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
  87. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
  88. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
  89. nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
  90. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
  91. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
  92. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
  93. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
  94. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +2 -2
  95. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
  96. nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
  97. nautobot/project-static/docs/development/apps/api/setup.html +2 -2
  98. nautobot/project-static/docs/development/apps/api/testing.html +2 -2
  99. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
  100. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
  101. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
  102. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
  103. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
  104. nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
  105. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
  106. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
  107. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
  108. nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
  109. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
  110. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
  111. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
  112. nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
  113. nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
  114. nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
  115. nautobot/project-static/docs/development/apps/index.html +2 -2
  116. nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
  117. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
  118. nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
  119. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
  120. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
  121. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
  122. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
  123. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +2 -2
  124. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +2 -2
  125. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +2 -2
  126. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +2 -2
  127. nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
  128. nautobot/project-static/docs/development/core/application-registry.html +2 -2
  129. nautobot/project-static/docs/development/core/best-practices.html +2 -2
  130. nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
  131. nautobot/project-static/docs/development/core/caching.html +2 -2
  132. nautobot/project-static/docs/development/core/controllers.html +2 -2
  133. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
  134. nautobot/project-static/docs/development/core/generic-views.html +2 -2
  135. nautobot/project-static/docs/development/core/getting-started.html +2 -2
  136. nautobot/project-static/docs/development/core/homepage.html +2 -2
  137. nautobot/project-static/docs/development/core/index.html +2 -2
  138. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +2 -2
  139. nautobot/project-static/docs/development/core/model-checklist.html +2 -2
  140. nautobot/project-static/docs/development/core/model-features.html +2 -2
  141. nautobot/project-static/docs/development/core/natural-keys.html +2 -2
  142. nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
  143. nautobot/project-static/docs/development/core/release-checklist.html +2 -2
  144. nautobot/project-static/docs/development/core/role-internals.html +2 -2
  145. nautobot/project-static/docs/development/core/settings.html +2 -2
  146. nautobot/project-static/docs/development/core/style-guide.html +2 -2
  147. nautobot/project-static/docs/development/core/templates.html +2 -2
  148. nautobot/project-static/docs/development/core/testing.html +2 -2
  149. nautobot/project-static/docs/development/core/ui-component-framework.html +2 -2
  150. nautobot/project-static/docs/development/core/user-preferences.html +2 -2
  151. nautobot/project-static/docs/development/index.html +2 -2
  152. nautobot/project-static/docs/development/jobs/getting-started.html +2 -6
  153. nautobot/project-static/docs/development/jobs/index.html +2 -2
  154. nautobot/project-static/docs/development/jobs/installation.html +2 -2
  155. nautobot/project-static/docs/development/jobs/job-extensions.html +2 -2
  156. nautobot/project-static/docs/development/jobs/job-logging.html +2 -2
  157. nautobot/project-static/docs/development/jobs/job-patterns.html +2 -2
  158. nautobot/project-static/docs/development/jobs/job-structure.html +2 -2
  159. nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
  160. nautobot/project-static/docs/development/jobs/testing.html +2 -2
  161. nautobot/project-static/docs/index.html +2 -2
  162. nautobot/project-static/docs/objects.inv +0 -0
  163. nautobot/project-static/docs/overview/application_stack.html +2 -2
  164. nautobot/project-static/docs/overview/design_philosophy.html +2 -2
  165. nautobot/project-static/docs/release-notes/index.html +2 -2
  166. nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
  167. nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
  168. nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
  169. nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
  170. nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
  171. nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
  172. nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
  173. nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
  174. nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
  175. nautobot/project-static/docs/release-notes/version-2.2.html +2 -2
  176. nautobot/project-static/docs/release-notes/version-2.3.html +2 -2
  177. nautobot/project-static/docs/release-notes/version-2.4.html +211 -2
  178. nautobot/project-static/docs/requirements.txt +1 -1
  179. nautobot/project-static/docs/search/search_index.json +1 -1
  180. nautobot/project-static/docs/sitemap.xml +299 -299
  181. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  182. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
  183. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
  184. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
  185. nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
  186. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +2 -2
  187. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +2 -2
  188. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
  189. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +2 -2
  190. nautobot/project-static/docs/user-guide/administration/guides/docker.html +2 -2
  191. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +2 -2
  192. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
  193. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
  194. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
  195. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
  196. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
  197. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +2 -2
  198. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
  199. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
  200. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
  201. nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
  202. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
  203. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
  204. nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
  205. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
  206. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
  207. nautobot/project-static/docs/user-guide/administration/security/index.html +2 -2
  208. nautobot/project-static/docs/user-guide/administration/security/notices.html +2 -2
  209. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +2 -2
  210. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
  211. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
  212. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
  213. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
  214. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
  215. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
  216. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
  217. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
  218. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
  219. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
  220. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
  221. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
  222. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
  223. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
  224. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
  225. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +2 -2
  226. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +2 -2
  227. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +2 -2
  228. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +2 -2
  229. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +2 -2
  230. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +2 -2
  231. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +2 -2
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +2 -2
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +2 -2
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +2 -2
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +2 -2
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +2 -2
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +2 -2
  275. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
  276. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
  277. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
  278. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
  280. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
  281. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
  282. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
  283. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
  284. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
  285. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
  286. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
  287. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
  288. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
  289. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
  290. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
  291. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
  292. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
  293. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
  294. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
  295. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
  296. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +2 -2
  297. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +2 -2
  298. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +2 -2
  299. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +2 -2
  300. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
  301. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
  302. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
  303. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
  304. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
  305. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
  306. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
  311. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +3 -3
  312. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
  313. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
  314. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
  315. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
  316. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +2 -2
  317. nautobot/project-static/docs/user-guide/index.html +2 -2
  318. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
  319. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
  320. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
  321. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
  322. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
  323. nautobot/project-static/docs/user-guide/platform-functionality/events.html +2 -2
  324. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
  325. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
  326. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
  327. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
  328. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
  329. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
  330. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
  331. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
  332. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
  333. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
  334. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +2 -2
  335. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +2 -2
  336. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +2 -2
  337. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
  338. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
  339. nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
  340. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +2 -2
  341. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
  342. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +2 -2
  343. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
  344. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
  345. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
  346. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
  347. nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
  348. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +2 -2
  349. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
  350. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +2 -2
  351. nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
  352. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
  353. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
  354. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
  355. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
  356. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
  357. nautobot/wireless/navigation.py +1 -1
  358. nautobot/wireless/tables.py +13 -5
  359. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/METADATA +1 -1
  360. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/RECORD +364 -357
  361. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/LICENSE.txt +0 -0
  362. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/NOTICE +0 -0
  363. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/WHEEL +0 -0
  364. {nautobot-2.4.11.dist-info → nautobot-2.4.13.dist-info}/entry_points.txt +0 -0
nautobot/dcim/views.py CHANGED
@@ -59,6 +59,7 @@ from nautobot.core.views.viewsets import NautobotUIViewSet
59
59
  from nautobot.dcim.choices import LocationDataToContactActionChoices
60
60
  from nautobot.dcim.forms import LocationMigrateDataToContactForm
61
61
  from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Team
62
+ from nautobot.extras.tables import DynamicGroupTable
62
63
  from nautobot.extras.views import ObjectChangeLogView, ObjectConfigContextView, ObjectDynamicGroupsView
63
64
  from nautobot.ipam.models import IPAddress, Prefix, Service, VLAN
64
65
  from nautobot.ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable, VRFDeviceAssignmentTable, VRFTable
@@ -70,8 +71,8 @@ from nautobot.wireless.models import (
70
71
  ControllerManagedDeviceGroupWirelessNetworkAssignment,
71
72
  )
72
73
  from nautobot.wireless.tables import (
74
+ BaseControllerManagedDeviceGroupWirelessNetworkAssignmentTable,
73
75
  ControllerManagedDeviceGroupRadioProfileAssignmentTable,
74
- ControllerManagedDeviceGroupWirelessNetworkAssignmentTable,
75
76
  DeviceGroupWirelessNetworkTable,
76
77
  RadioProfileTable,
77
78
  )
@@ -1701,7 +1702,39 @@ class DeviceView(generic.ObjectView):
1701
1702
  "tenant__tenant_group",
1702
1703
  ).prefetch_related("images", "software_image_files")
1703
1704
 
1704
- object_detail_content = object_detail.ObjectDetailContent(
1705
+ class DeviceDetailContent(object_detail.ObjectDetailContent):
1706
+ """
1707
+ Override base ObjectDetailContent to render dynamic-groups table as a separate view/tab instead of inline.
1708
+ """
1709
+
1710
+ def __init__(self, **kwargs):
1711
+ super().__init__(**kwargs)
1712
+ # Remove inline tab definition
1713
+ for tab in list(self._tabs):
1714
+ if isinstance(tab, object_detail._ObjectDetailGroupsTab):
1715
+ self._tabs.remove(tab)
1716
+ # Add distinct-view tab definition
1717
+ self._tabs.append(
1718
+ object_detail.DistinctViewTab(
1719
+ weight=object_detail.Tab.WEIGHT_GROUPS_TAB,
1720
+ tab_id="dynamic_groups",
1721
+ label="Dynamic Groups",
1722
+ url_name="dcim:device_dynamicgroups",
1723
+ related_object_attribute="dynamic_groups",
1724
+ panels=(
1725
+ object_detail.ObjectsTablePanel(
1726
+ weight=100,
1727
+ table_class=DynamicGroupTable,
1728
+ table_attribute="dynamic_groups",
1729
+ exclude_columns=["content_type"],
1730
+ add_button_route=None,
1731
+ related_field_name="member_id",
1732
+ ),
1733
+ ),
1734
+ )
1735
+ )
1736
+
1737
+ object_detail_content = DeviceDetailContent(
1705
1738
  extra_buttons=(
1706
1739
  object_detail.DropdownButton(
1707
1740
  weight=100,
@@ -2188,7 +2221,7 @@ class DeviceChangeLogView(ObjectChangeLogView):
2188
2221
  base_template = "dcim/device/base.html"
2189
2222
 
2190
2223
 
2191
- class DeviceDynamicGroupsView(ObjectDynamicGroupsView): # 3.0 TODO: remove, deprecated in 2.3
2224
+ class DeviceDynamicGroupsView(ObjectDynamicGroupsView):
2192
2225
  base_template = "dcim/device/base.html"
2193
2226
 
2194
2227
 
@@ -2238,7 +2271,7 @@ class DeviceWirelessView(generic.ObjectView):
2238
2271
  wireless_networks = ControllerManagedDeviceGroupWirelessNetworkAssignment.objects.filter(
2239
2272
  controller_managed_device_group=controller_managed_device_group
2240
2273
  ).select_related("wireless_network", "controller_managed_device_group", "vlan")
2241
- wireless_networks_table = ControllerManagedDeviceGroupWirelessNetworkAssignmentTable(
2274
+ wireless_networks_table = BaseControllerManagedDeviceGroupWirelessNetworkAssignmentTable(
2242
2275
  data=wireless_networks, user=request.user, orderable=False
2243
2276
  )
2244
2277
  wireless_networks_table.columns.hide("controller_managed_device_group")
@@ -3907,151 +3940,102 @@ class InterfaceConnectionsListView(ConnectionsListView):
3907
3940
  #
3908
3941
 
3909
3942
 
3910
- class VirtualChassisListView(generic.ObjectListView):
3911
- queryset = VirtualChassis.objects.all()
3912
- table = tables.VirtualChassisTable
3913
- filterset = filters.VirtualChassisFilterSet
3914
- filterset_form = forms.VirtualChassisFilterForm
3915
-
3916
-
3917
- class VirtualChassisView(generic.ObjectView):
3943
+ class VirtualChassisUIViewSet(NautobotUIViewSet):
3944
+ bulk_update_form_class = forms.VirtualChassisBulkEditForm
3945
+ filterset_class = filters.VirtualChassisFilterSet
3946
+ filterset_form_class = forms.VirtualChassisFilterForm
3947
+ form_class = forms.VirtualChassisCreateForm
3948
+ serializer_class = serializers.VirtualChassisSerializer
3949
+ table_class = tables.VirtualChassisTable
3918
3950
  queryset = VirtualChassis.objects.all()
3919
3951
 
3920
3952
  def get_extra_context(self, request, instance):
3921
- members = Device.objects.restrict(request.user).filter(virtual_chassis=instance)
3922
-
3923
- return {"members": members, **super().get_extra_context(request, instance)}
3924
-
3925
-
3926
- class VirtualChassisCreateView(generic.ObjectEditView):
3927
- queryset = VirtualChassis.objects.all()
3928
- model_form = forms.VirtualChassisCreateForm
3929
- template_name = "dcim/virtualchassis_add.html"
3930
-
3931
-
3932
- class VirtualChassisEditView(ObjectPermissionRequiredMixin, GetReturnURLMixin, View):
3933
- queryset = VirtualChassis.objects.all()
3934
-
3935
- def get_required_permission(self):
3936
- return "dcim.change_virtualchassis"
3937
-
3938
- def get(self, request, pk):
3939
- virtual_chassis = get_object_or_404(self.queryset, pk=pk)
3940
- VCMemberFormSet = modelformset_factory(
3941
- model=Device,
3942
- form=forms.DeviceVCMembershipForm,
3943
- formset=forms.BaseVCMemberFormSet,
3944
- extra=0,
3945
- )
3946
- members_queryset = virtual_chassis.members.select_related("rack").order_by("vc_position")
3947
-
3948
- vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
3949
- vc_form.fields["master"].queryset = members_queryset
3950
- formset = VCMemberFormSet(queryset=members_queryset)
3951
-
3952
- return render(
3953
- request,
3954
- "dcim/virtualchassis_edit.html",
3955
- {
3956
- "vc_form": vc_form,
3957
- "formset": formset,
3958
- "return_url": self.get_return_url(request, virtual_chassis),
3959
- },
3960
- )
3961
-
3962
- def post(self, request, pk):
3963
- virtual_chassis = get_object_or_404(self.queryset, pk=pk)
3964
- VCMemberFormSet = modelformset_factory(
3965
- model=Device,
3966
- form=forms.DeviceVCMembershipForm,
3967
- formset=forms.BaseVCMemberFormSet,
3968
- extra=0,
3969
- )
3970
- members_queryset = virtual_chassis.members.select_related("rack").order_by("vc_position")
3971
-
3972
- vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
3973
- vc_form.fields["master"].queryset = members_queryset
3974
- formset = VCMemberFormSet(request.POST, queryset=members_queryset)
3975
-
3976
- if vc_form.is_valid() and formset.is_valid():
3977
- with transaction.atomic():
3978
- # Save the VirtualChassis
3979
- vc_form.save()
3980
-
3981
- # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
3982
- # duplicate positions. Then save each member instance.
3983
- members = formset.save(commit=False)
3984
- devices = Device.objects.filter(pk__in=[m.pk for m in members])
3985
- for device in devices:
3986
- device.vc_position = None
3987
- device.save()
3988
- for member in members:
3989
- member.save()
3990
-
3991
- return redirect(virtual_chassis.get_absolute_url())
3992
-
3993
- return render(
3994
- request,
3995
- "dcim/virtualchassis_edit.html",
3996
- {
3997
- "vc_form": vc_form,
3998
- "formset": formset,
3999
- "return_url": self.get_return_url(request, virtual_chassis),
4000
- },
4001
- )
4002
-
3953
+ context = super().get_extra_context(request, instance)
4003
3954
 
4004
- class VirtualChassisDeleteView(generic.ObjectDeleteView):
4005
- queryset = VirtualChassis.objects.all()
3955
+ if self.action == "update":
3956
+ VCMemberFormSet = modelformset_factory(
3957
+ model=Device,
3958
+ form=forms.DeviceVCMembershipForm,
3959
+ formset=forms.BaseVCMemberFormSet,
3960
+ extra=0,
3961
+ )
3962
+ members_queryset = instance.members.select_related("rack").order_by("vc_position")
4006
3963
 
3964
+ if request.method == "POST":
3965
+ formset = VCMemberFormSet(request.POST, queryset=members_queryset)
3966
+ else:
3967
+ formset = VCMemberFormSet(queryset=members_queryset)
4007
3968
 
4008
- class VirtualChassisAddMemberView(ObjectPermissionRequiredMixin, GetReturnURLMixin, View):
4009
- queryset = VirtualChassis.objects.all()
3969
+ vc_form = forms.VirtualChassisForm(instance=instance)
3970
+ vc_form.fields["master"].queryset = members_queryset
4010
3971
 
4011
- def get_required_permission(self):
4012
- return "dcim.change_virtualchassis"
4013
-
4014
- def get(self, request, pk):
4015
- virtual_chassis = get_object_or_404(self.queryset, pk=pk)
3972
+ context.update(
3973
+ {
3974
+ "formset": formset,
3975
+ "vc_form": vc_form,
3976
+ "return_url": self.get_return_url(request, instance),
3977
+ }
3978
+ )
4016
3979
 
4017
- initial_data = {k: request.GET[k] for k in request.GET}
4018
- member_select_form = forms.VCMemberSelectForm(initial=initial_data)
4019
- membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
3980
+ elif self.action == "retrieve":
3981
+ members = Device.objects.restrict(request.user).filter(virtual_chassis=instance)
3982
+ context.update({"members": members})
4020
3983
 
4021
- return render(
4022
- request,
4023
- "dcim/virtualchassis_add_member.html",
4024
- {
4025
- "virtual_chassis": virtual_chassis,
4026
- "member_select_form": member_select_form,
4027
- "membership_form": membership_form,
4028
- "return_url": self.get_return_url(request, virtual_chassis),
4029
- },
4030
- )
3984
+ return context
4031
3985
 
4032
- def post(self, request, pk):
4033
- virtual_chassis = get_object_or_404(self.queryset, pk=pk)
3986
+ def form_save(self, form, **kwargs):
3987
+ obj = super().form_save(form, **kwargs)
4034
3988
 
4035
- member_select_form = forms.VCMemberSelectForm(request.POST)
3989
+ if self.action == "update":
3990
+ context = self.get_extra_context(self.request, obj)
3991
+ formset = context.get("formset")
4036
3992
 
4037
- if member_select_form.is_valid():
4038
- device = member_select_form.cleaned_data["device"]
4039
- device.virtual_chassis = virtual_chassis
4040
- data = {k: request.POST[k] for k in ["vc_position", "vc_priority"]}
4041
- membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
3993
+ if formset.is_valid():
3994
+ with transaction.atomic():
3995
+ members = formset.save(commit=False)
3996
+ # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
3997
+ # duplicate positions. Then save each member instance.
3998
+ Device.objects.filter(pk__in=[m.pk for m in members]).update(vc_position=None)
3999
+ for member in members:
4000
+ member.save()
4001
+ else:
4002
+ raise ValidationError(formset.errors)
4042
4003
 
4043
- if membership_form.is_valid():
4044
- membership_form.save()
4045
- msg = format_html('Added member <a href="{}">{}</a>', device.get_absolute_url(), device)
4046
- messages.success(request, msg)
4004
+ return obj
4047
4005
 
4048
- if "_addanother" in request.POST:
4049
- return redirect(request.get_full_path())
4006
+ @action(
4007
+ detail=True,
4008
+ methods=["get", "post"],
4009
+ url_path="add-member",
4010
+ url_name="add_member",
4011
+ custom_view_base_action="change",
4012
+ custom_view_additional_permissions=["dcim.change_virtualchassis"],
4013
+ )
4014
+ def add_member(self, request, pk=None):
4015
+ virtual_chassis = self.get_object()
4050
4016
 
4051
- return redirect(self.get_return_url(request, device))
4017
+ if request.method == "POST":
4018
+ member_select_form = forms.VCMemberSelectForm(request.POST)
4019
+ if member_select_form.is_valid():
4020
+ device = member_select_form.cleaned_data["device"]
4021
+ device.virtual_chassis = virtual_chassis
4022
+ data = {k: request.POST[k] for k in ["vc_position", "vc_priority"]}
4023
+ membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
4024
+
4025
+ if membership_form.is_valid():
4026
+ membership_form.save()
4027
+ msg = format_html('Added member <a href="{}">{}</a>', device.get_absolute_url(), device)
4028
+ messages.success(request, msg)
4052
4029
 
4030
+ if "_addanother" in request.POST:
4031
+ return redirect(request.get_full_path())
4032
+ return redirect(self.get_return_url(request, device))
4033
+ else:
4034
+ membership_form = forms.DeviceVCMembershipForm(data=request.POST)
4053
4035
  else:
4054
- membership_form = forms.DeviceVCMembershipForm(data=request.POST)
4036
+ initial_data = {k: request.GET[k] for k in request.GET}
4037
+ member_select_form = forms.VCMemberSelectForm(initial=initial_data)
4038
+ membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
4055
4039
 
4056
4040
  return render(
4057
4041
  request,
@@ -4120,24 +4104,6 @@ class VirtualChassisRemoveMemberView(ObjectPermissionRequiredMixin, GetReturnURL
4120
4104
  )
4121
4105
 
4122
4106
 
4123
- class VirtualChassisBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
4124
- queryset = VirtualChassis.objects.all()
4125
- table = tables.VirtualChassisTable
4126
-
4127
-
4128
- class VirtualChassisBulkEditView(generic.BulkEditView):
4129
- queryset = VirtualChassis.objects.all()
4130
- filterset = filters.VirtualChassisFilterSet
4131
- table = tables.VirtualChassisTable
4132
- form = forms.VirtualChassisBulkEditForm
4133
-
4134
-
4135
- class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
4136
- queryset = VirtualChassis.objects.all()
4137
- filterset = filters.VirtualChassisFilterSet
4138
- table = tables.VirtualChassisTable
4139
-
4140
-
4141
4107
  #
4142
4108
  # Power panels
4143
4109
  #
@@ -4687,25 +4653,69 @@ class ControllerUIViewSet(NautobotUIViewSet):
4687
4653
  table_class = tables.ControllerTable
4688
4654
  template_name = "dcim/controller_create.html"
4689
4655
 
4690
- def get_extra_context(self, request, instance):
4691
- context = super().get_extra_context(request, instance)
4692
-
4693
- if self.action == "retrieve" and instance:
4694
- devices = Device.objects.restrict(request.user).filter(controller_managed_device_group__controller=instance)
4695
- devices_table = tables.DeviceTable(devices)
4696
- devices_table.columns.show("controller_managed_device_group")
4697
- devices_table.columns.show("capabilities")
4698
- devices_table.sequence = ("name", "controller_managed_device_group", "capabilities", "...")
4699
-
4700
- paginate = {
4701
- "paginator_class": EnhancedPaginator,
4702
- "per_page": get_paginate_count(request),
4703
- }
4704
- RequestConfig(request, paginate).configure(devices_table)
4705
-
4706
- context["devices_table"] = devices_table
4707
-
4708
- return context
4656
+ object_detail_content = object_detail.ObjectDetailContent(
4657
+ panels=(
4658
+ object_detail.ObjectFieldsPanel(
4659
+ section=SectionChoices.LEFT_HALF,
4660
+ weight=100,
4661
+ fields="__all__",
4662
+ value_transforms={
4663
+ "capabilities": [helpers.label_list],
4664
+ },
4665
+ exclude_fields=[
4666
+ "external_integration",
4667
+ "controller_device",
4668
+ "controller_device_redundancy_group",
4669
+ ],
4670
+ ),
4671
+ object_detail.ObjectFieldsPanel(
4672
+ section=SectionChoices.RIGHT_HALF,
4673
+ weight=200,
4674
+ label="Integration",
4675
+ fields=[
4676
+ "external_integration",
4677
+ "controller_device",
4678
+ "controller_device_redundancy_group",
4679
+ ],
4680
+ ),
4681
+ object_detail.ObjectsTablePanel(
4682
+ section=SectionChoices.FULL_WIDTH,
4683
+ weight=100,
4684
+ table_class=tables.DeviceTable,
4685
+ table_title="Managed Devices",
4686
+ table_filter="controller_managed_device_group__controller",
4687
+ include_columns=[
4688
+ "capabilities",
4689
+ "controller_managed_device_group",
4690
+ "manufacturer",
4691
+ ],
4692
+ related_field_name="controller",
4693
+ add_button_route=None,
4694
+ ),
4695
+ ),
4696
+ extra_tabs=(
4697
+ object_detail.DistinctViewTab(
4698
+ weight=700,
4699
+ tab_id="wireless_networks",
4700
+ url_name="dcim:controller_wirelessnetworks",
4701
+ label="Wireless Networks",
4702
+ related_object_attribute="wireless_network_assignments",
4703
+ panels=(
4704
+ object_detail.ObjectsTablePanel(
4705
+ section=SectionChoices.FULL_WIDTH,
4706
+ weight=100,
4707
+ table_title="Wireless Networks",
4708
+ table_class=BaseControllerManagedDeviceGroupWirelessNetworkAssignmentTable,
4709
+ table_filter="controller_managed_device_group__controller",
4710
+ tab_id="wireless_networks",
4711
+ add_button_route=None,
4712
+ select_related_fields=["wireless_network"],
4713
+ exclude_columns=["controller"],
4714
+ ),
4715
+ ),
4716
+ ),
4717
+ ),
4718
+ )
4709
4719
 
4710
4720
  @action(
4711
4721
  detail=True,
@@ -4716,28 +4726,7 @@ class ControllerUIViewSet(NautobotUIViewSet):
4716
4726
  custom_view_additional_permissions=["wireless.view_controllermanageddevicegroupwirelessnetworkassignment"],
4717
4727
  )
4718
4728
  def wirelessnetworks(self, request, *args, **kwargs):
4719
- instance = self.get_object()
4720
- controller_managed_device_groups = instance.controller_managed_device_groups.restrict(
4721
- request.user, "view"
4722
- ).values_list("pk", flat=True)
4723
- wireless_networks = ControllerManagedDeviceGroupWirelessNetworkAssignment.objects.filter(
4724
- controller_managed_device_group__in=list(controller_managed_device_groups)
4725
- ).select_related("wireless_network")
4726
- wireless_networks_table = ControllerManagedDeviceGroupWirelessNetworkAssignmentTable(
4727
- data=wireless_networks, user=request.user, orderable=False
4728
- )
4729
- wireless_networks_table.columns.hide("controller")
4730
-
4731
- RequestConfig(
4732
- request, paginate={"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
4733
- ).configure(wireless_networks_table)
4734
-
4735
- return Response(
4736
- {
4737
- "wireless_networks_table": wireless_networks_table,
4738
- "active_tab": "wireless-networks",
4739
- }
4740
- )
4729
+ return Response({})
4741
4730
 
4742
4731
 
4743
4732
  class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
@@ -720,8 +720,8 @@ def refresh_job_code_from_repository(repository_slug, skip_reimport=False, ignor
720
720
  After cloning/updating/deleting a GitRepository on disk, call this function to reload and reregister its Python.
721
721
 
722
722
  Args:
723
- repository_slug (str): Repository slug (directory in GIT_ROOT) that was populated, updated, or deleted.
724
- skip_reimport (bool): If True, unload existing jobs from this repository but do not re-import them.
723
+ repository_slug (str): Repository directory in GIT_ROOT that was updated or deleted.
724
+ skip_reimport (bool): If True, unload existing code from this repository but do not re-import it.
725
725
  ignore_import_errors (bool): If True, any exceptions raised in the import will be caught and logged.
726
726
  If False, exceptions will be re-raised after logging.
727
727
  """
@@ -0,0 +1,208 @@
1
+ from django.template import Context
2
+ from django.template.loader import render_to_string
3
+ from django.utils.html import format_html, format_html_join
4
+ from django.utils.safestring import mark_safe
5
+
6
+ from nautobot.core.templatetags import helpers
7
+ from nautobot.core.ui.object_detail import Button, KeyValueTablePanel, ObjectFieldsPanel
8
+ from nautobot.core.views.utils import get_obj_from_context
9
+
10
+
11
+ class JobRunScheduleButton(Button):
12
+ """
13
+ A custom button for running or scheduling a job.
14
+
15
+ This button is rendered only if the user has the 'extras.run_job' permission.
16
+ It also disables itself (via HTML 'disabled' attribute) if the related object is not
17
+ installed or not enabled.
18
+ """
19
+
20
+ def get_extra_context(self, context):
21
+ """Inject dynamic attributes (e.g. 'disabled') based on object state into the rendering context."""
22
+ extra_context = super().get_extra_context(context)
23
+ obj = context.get("object")
24
+ if not obj.installed or not obj.enabled:
25
+ if extra_context["attributes"] is None:
26
+ extra_context["attributes"] = {}
27
+ extra_context["attributes"]["disabled"] = "disabled"
28
+ return extra_context
29
+
30
+
31
+ class JobKeyValueOverrideValueTablePanel(KeyValueTablePanel):
32
+ """A table panel for displaying key-value pairs of job-related attributes, along with any override values defined on the job object."""
33
+
34
+ def _render_overridden_text(self, text):
35
+ """
36
+ Render a simple overridden value inside a muted <span>, with a customizable prefix.
37
+
38
+ Args:
39
+ value (str): The content to display.
40
+ prefix (str): The label shown before the value (default: 'default is').
41
+ """
42
+ return format_html('<span class="text-muted">overridden; default is {}</span>', mark_safe(text)) # noqa: S308
43
+
44
+ def _render_overridden_block(self, content):
45
+ """
46
+ Render a more complex block of HTML content (like rendered markdown or JSON)
47
+ in a div with a muted label indicating it's an overridden value.
48
+ """
49
+ return format_html('<div class="text-muted">overridden; default is:<br>{}</div>', content)
50
+
51
+ def render_description_default(self, default_value):
52
+ """
53
+ Render a description field's default value as markdown.
54
+
55
+ If the default value is a string, it is rendered as markdown;
56
+ otherwise, a placeholder is shown.
57
+ """
58
+ rendered = (
59
+ helpers.render_markdown(default_value)
60
+ if isinstance(default_value, str)
61
+ else helpers.placeholder(default_value)
62
+ )
63
+ return self._render_overridden_block(rendered)
64
+
65
+ def render_time_limit_default(self, default_value, system_default_value, override=True):
66
+ """
67
+ Render a time limit value, falling back to the system default if needed.
68
+ """
69
+ message = "{} seconds" if default_value > 0 else "{} seconds (system default)"
70
+ value = default_value if default_value > 0 else system_default_value
71
+ if not override:
72
+ return message.format(value)
73
+ return self._render_overridden_text(message.format(value))
74
+
75
+ def render_job_queues_default(self, obj):
76
+ """
77
+ Render the job's default task queues as JSON.
78
+
79
+ Attempts to retrieve the `task_queues` from the job's class and render
80
+ it using a JSON template. Falls back to a placeholder on error.
81
+ """
82
+ try:
83
+ data = obj.job_class.task_queues
84
+ rendered = render_to_string("extras/inc/json_data.html", {"data": data, "format": "json"})
85
+ except Exception:
86
+ rendered = helpers.placeholder(None)
87
+ return self._render_overridden_block(rendered)
88
+
89
+ def render_default_job_queue_default(self, obj):
90
+ """
91
+ Render the default job queue name from the job class.
92
+
93
+ Retrieves the first queue from `task_queues` if available.
94
+ Falls back to a placeholder if no queues are present.
95
+ """
96
+ try:
97
+ queue = obj.job_class.task_queues[0]
98
+ except (AttributeError, IndexError, TypeError):
99
+ queue = helpers.placeholder("not specified")
100
+ return self._render_overridden_text(queue)
101
+
102
+ def render_boolean_default(self, default_value):
103
+ """Render a boolean default value using a standardized visual format."""
104
+ return self._render_overridden_text(helpers.render_boolean(default_value))
105
+
106
+ def render_override_value(self, key, obj):
107
+ """Render the override value for a given key on a job object."""
108
+ override_attr = f"{key}_override"
109
+
110
+ if not getattr(obj, override_attr, None):
111
+ return ""
112
+
113
+ if not obj.installed:
114
+ return self._render_overridden_text("default is unknown (not currently installed)")
115
+
116
+ try:
117
+ default_value = getattr(obj.job_class, key)
118
+ except Exception:
119
+ default_value = "unknown"
120
+
121
+ renderers = {
122
+ "description": self.render_description_default,
123
+ "soft_time_limit": lambda v: self.render_time_limit_default(
124
+ v, helpers.settings_or_config("CELERY_TASK_SOFT_TIME_LIMIT")
125
+ ),
126
+ "time_limit": lambda v: self.render_time_limit_default(
127
+ v, helpers.settings_or_config("CELERY_TASK_TIME_LIMIT")
128
+ ),
129
+ "job_queues": lambda _: self.render_job_queues_default(obj),
130
+ "default_job_queue": lambda _: self.render_default_job_queue_default(obj),
131
+ }
132
+
133
+ if key in renderers:
134
+ return renderers[key](default_value)
135
+
136
+ if isinstance(default_value, bool):
137
+ return self.render_boolean_default(default_value)
138
+
139
+ return self._render_overridden_text(f'"{default_value}"')
140
+
141
+ def render_job_queues_list(self, value):
142
+ """Renders a <ul> HTML list of job queues with hyperlinks, or a placeholder if none exist."""
143
+ if not value or not value.exists():
144
+ return helpers.placeholder(None)
145
+
146
+ items = format_html_join("\n", "<li>{}</li>", ((helpers.hyperlinked_object(q),) for q in value.all()))
147
+ return format_html("<ul>{}</ul>", items)
148
+
149
+ def render_body_content(self, context: Context):
150
+ """Render the body content of the panel as a table of key-value rows, including any override information."""
151
+ data = self.get_data(context)
152
+ obj = get_obj_from_context(context)
153
+
154
+ if not data:
155
+ return format_html('<tr><td colspan="2">{}</td></tr>', helpers.placeholder(data))
156
+
157
+ result = format_html("")
158
+ panel_label = helpers.slugify(self.label or "")
159
+ for key, value in data.items():
160
+ key_display = self.render_key(key, value, context)
161
+ override_value_display = self.render_override_value(key, obj)
162
+ if value_display := self.render_value(key, value, context):
163
+ if value_display is helpers.HTML_NONE:
164
+ value_tag = value_display
165
+ else:
166
+ value_tag = format_html(
167
+ """
168
+ <span class="hover_copy">
169
+ <span id="{unique_id}_value_{key}">{value}</span>
170
+ <button class="btn btn-inline btn-default hover_copy_button" data-clipboard-target="#{unique_id}_value_{key}">
171
+ <span class="mdi mdi-content-copy"></span>
172
+ </button>
173
+ </span>
174
+ """,
175
+ # key might not be globally unique in a page, but is unique to a panel;
176
+ # Hence we add the panel label to make it globally unique to the page
177
+ unique_id=panel_label,
178
+ key=helpers.slugify(key),
179
+ value=value_display,
180
+ )
181
+ result += format_html(
182
+ "<tr><td>{}</td><td>{}</td><td>{}</td></tr>",
183
+ key_display,
184
+ value_tag,
185
+ override_value_display,
186
+ )
187
+
188
+ return result
189
+
190
+
191
+ class JobObjectFieldsPanel(ObjectFieldsPanel, JobKeyValueOverrideValueTablePanel):
192
+ """
193
+ ObjectFieldsPanel that renders its fields in a 3-column layout.
194
+ Inherits behavior from ObjectFieldsPanel but overrides rendering with JobKeyValueOverrideValueTablePanel.
195
+ """
196
+
197
+ def render_value(self, key, value, context: Context):
198
+ if key == "job_queues":
199
+ return self.render_job_queues_list(value)
200
+ if key == "soft_time_limit":
201
+ value = self.render_time_limit_default(
202
+ value, helpers.settings_or_config("CELERY_TASK_SOFT_TIME_LIMIT"), override=False
203
+ )
204
+ if key == "time_limit":
205
+ value = self.render_time_limit_default(
206
+ value, helpers.settings_or_config("CELERY_TASK_TIME_LIMIT"), override=False
207
+ )
208
+ return super().render_value(key, value, context)