nautobot 2.4.10__py3-none-any.whl → 2.4.11__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 (416) hide show
  1. nautobot/cloud/tests/test_views.py +13 -1
  2. nautobot/cloud/views.py +39 -9
  3. nautobot/core/celery/__init__.py +21 -0
  4. nautobot/core/celery/encoders.py +3 -0
  5. nautobot/core/forms/forms.py +4 -1
  6. nautobot/core/jobs/bulk_actions.py +8 -8
  7. nautobot/core/jobs/cleanup.py +11 -0
  8. nautobot/core/management/commands/generate_test_data.py +2 -1
  9. nautobot/core/templates/generic/object_retrieve.html +1 -1
  10. nautobot/core/testing/mixins.py +19 -1
  11. nautobot/core/testing/views.py +104 -8
  12. nautobot/core/tests/test_jobs.py +20 -4
  13. nautobot/core/tests/test_utils.py +193 -0
  14. nautobot/core/tests/test_views_utils.py +53 -2
  15. nautobot/core/ui/object_detail.py +4 -0
  16. nautobot/core/utils/lookup.py +4 -2
  17. nautobot/core/utils/module_loading.py +86 -58
  18. nautobot/core/views/generic.py +2 -12
  19. nautobot/core/views/mixins.py +19 -1
  20. nautobot/core/views/renderers.py +4 -13
  21. nautobot/core/views/utils.py +16 -0
  22. nautobot/dcim/api/serializers.py +13 -0
  23. nautobot/dcim/api/urls.py +1 -0
  24. nautobot/dcim/api/views.py +20 -0
  25. nautobot/dcim/apps.py +1 -0
  26. nautobot/dcim/factory.py +11 -0
  27. nautobot/dcim/filters/__init__.py +110 -0
  28. nautobot/dcim/forms.py +205 -19
  29. nautobot/dcim/migrations/0070_modulefamily_models.py +92 -0
  30. nautobot/dcim/models/__init__.py +2 -0
  31. nautobot/dcim/models/device_component_templates.py +14 -0
  32. nautobot/dcim/models/device_components.py +13 -1
  33. nautobot/dcim/models/devices.py +62 -0
  34. nautobot/dcim/navigation.py +16 -0
  35. nautobot/dcim/tables/__init__.py +2 -0
  36. nautobot/dcim/tables/devices.py +48 -0
  37. nautobot/dcim/tables/devicetypes.py +35 -1
  38. nautobot/dcim/tables/template_code.py +2 -0
  39. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -90
  40. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
  41. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +1 -63
  42. nautobot/dcim/templates/dcim/location.html +2 -249
  43. nautobot/dcim/templates/dcim/location_edit.html +2 -38
  44. nautobot/dcim/templates/dcim/location_retrieve.html +249 -0
  45. nautobot/dcim/templates/dcim/location_update.html +38 -0
  46. nautobot/dcim/templates/dcim/module_update.html +1 -0
  47. nautobot/dcim/templates/dcim/modulebay_retrieve.html +93 -1
  48. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +31 -0
  49. nautobot/dcim/templates/dcim/moduletype_retrieve.html +6 -0
  50. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -160
  51. nautobot/dcim/tests/test_api.py +35 -0
  52. nautobot/dcim/tests/test_filters.py +102 -3
  53. nautobot/dcim/tests/test_models.py +146 -0
  54. nautobot/dcim/tests/test_views.py +70 -97
  55. nautobot/dcim/urls.py +4 -22
  56. nautobot/dcim/views.py +439 -153
  57. nautobot/extras/api/views.py +9 -2
  58. nautobot/extras/datasources/git.py +11 -3
  59. nautobot/extras/forms/forms.py +9 -5
  60. nautobot/extras/jobs.py +4 -2
  61. nautobot/extras/models/datasources.py +5 -8
  62. nautobot/extras/models/jobs.py +5 -0
  63. nautobot/extras/plugins/__init__.py +3 -0
  64. nautobot/extras/tables.py +40 -3
  65. nautobot/extras/templates/extras/configcontext.html +2 -220
  66. nautobot/extras/templates/extras/configcontext_edit.html +2 -50
  67. nautobot/extras/templates/extras/configcontext_retrieve.html +2 -0
  68. nautobot/extras/templates/extras/configcontext_update.html +50 -0
  69. nautobot/extras/templates/extras/configcontextschema.html +2 -48
  70. nautobot/extras/templates/extras/configcontextschema_edit.html +2 -19
  71. nautobot/extras/templates/extras/configcontextschema_retrieve.html +48 -0
  72. nautobot/extras/templates/extras/configcontextschema_update.html +19 -0
  73. nautobot/extras/templates/extras/inc/configcontext_data.html +1 -0
  74. nautobot/extras/templates/extras/inc/json_data.html +1 -1
  75. nautobot/extras/templates/extras/inc/json_format.html +2 -2
  76. nautobot/extras/templates/extras/job_edit.html +12 -6
  77. nautobot/extras/templates/extras/tag.html +2 -52
  78. nautobot/extras/templates/extras/tag_edit.html +2 -15
  79. nautobot/extras/templates/extras/tag_retrieve.html +52 -0
  80. nautobot/extras/templates/extras/tag_update.html +15 -0
  81. nautobot/extras/templates/extras/team_retrieve.html +2 -2
  82. nautobot/extras/tests/test_api.py +15 -15
  83. nautobot/extras/tests/test_filters.py +4 -4
  84. nautobot/extras/tests/test_jobs.py +23 -10
  85. nautobot/extras/tests/test_models.py +19 -8
  86. nautobot/extras/tests/test_plugins.py +6 -3
  87. nautobot/extras/tests/test_views.py +66 -11
  88. nautobot/extras/urls.py +4 -134
  89. nautobot/extras/views.py +113 -158
  90. nautobot/ipam/models.py +19 -4
  91. nautobot/ipam/tables.py +19 -0
  92. nautobot/ipam/templates/ipam/vlan.html +2 -84
  93. nautobot/ipam/templates/ipam/vlan_edit.html +2 -24
  94. nautobot/ipam/templates/ipam/vlan_retrieve.html +84 -0
  95. nautobot/ipam/templates/ipam/vlan_update.html +24 -0
  96. nautobot/ipam/tests/test_views.py +5 -0
  97. nautobot/ipam/urls.py +1 -21
  98. nautobot/ipam/views.py +45 -70
  99. nautobot/project-static/docs/404.html +31 -8
  100. nautobot/project-static/docs/apps/index.html +31 -8
  101. nautobot/project-static/docs/apps/nautobot-apps.html +31 -8
  102. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +31 -8
  103. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +31 -8
  104. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +31 -8
  105. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +31 -8
  106. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +31 -8
  107. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +31 -8
  108. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +31 -8
  109. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +31 -8
  110. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +31 -8
  111. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +31 -8
  112. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +31 -8
  113. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +31 -8
  114. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +31 -8
  115. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +31 -8
  116. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +31 -8
  117. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +31 -8
  118. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +31 -8
  119. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +31 -8
  120. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +31 -8
  121. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +120 -8
  122. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +31 -8
  123. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +31 -8
  124. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +31 -8
  125. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +31 -8
  126. nautobot/project-static/docs/development/apps/api/configuration-view.html +31 -8
  127. nautobot/project-static/docs/development/apps/api/database-backend-config.html +31 -8
  128. nautobot/project-static/docs/development/apps/api/models/django-admin.html +31 -8
  129. nautobot/project-static/docs/development/apps/api/models/global-search.html +31 -8
  130. nautobot/project-static/docs/development/apps/api/models/graphql.html +31 -8
  131. nautobot/project-static/docs/development/apps/api/models/index.html +31 -8
  132. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +40 -8
  133. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +31 -8
  134. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +31 -8
  135. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +31 -8
  136. nautobot/project-static/docs/development/apps/api/platform-features/index.html +31 -8
  137. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +31 -8
  138. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +31 -8
  139. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +31 -8
  140. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +32 -9
  141. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +31 -8
  142. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +31 -8
  143. nautobot/project-static/docs/development/apps/api/prometheus.html +31 -8
  144. nautobot/project-static/docs/development/apps/api/setup.html +31 -8
  145. nautobot/project-static/docs/development/apps/api/testing.html +31 -8
  146. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +31 -8
  147. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +31 -8
  148. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +31 -8
  149. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +31 -8
  150. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +31 -8
  151. nautobot/project-static/docs/development/apps/api/views/base-template.html +31 -8
  152. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +31 -8
  153. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +31 -8
  154. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +31 -8
  155. nautobot/project-static/docs/development/apps/api/views/index.html +31 -8
  156. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +31 -8
  157. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -8
  158. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +31 -8
  159. nautobot/project-static/docs/development/apps/api/views/notes.html +31 -8
  160. nautobot/project-static/docs/development/apps/api/views/rest-api.html +31 -8
  161. nautobot/project-static/docs/development/apps/api/views/urls.html +31 -8
  162. nautobot/project-static/docs/development/apps/index.html +31 -8
  163. nautobot/project-static/docs/development/apps/migration/code-updates.html +31 -8
  164. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +31 -8
  165. nautobot/project-static/docs/development/apps/migration/from-v1.html +31 -8
  166. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +31 -8
  167. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +31 -8
  168. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +31 -8
  169. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +31 -8
  170. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +31 -8
  171. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +31 -8
  172. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +31 -8
  173. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +31 -8
  174. nautobot/project-static/docs/development/apps/porting-from-netbox.html +31 -8
  175. nautobot/project-static/docs/development/core/application-registry.html +31 -8
  176. nautobot/project-static/docs/development/core/best-practices.html +31 -8
  177. nautobot/project-static/docs/development/core/bootstrap-ui.html +31 -8
  178. nautobot/project-static/docs/development/core/caching.html +31 -8
  179. nautobot/project-static/docs/development/core/controllers.html +31 -8
  180. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +31 -8
  181. nautobot/project-static/docs/development/core/generic-views.html +31 -8
  182. nautobot/project-static/docs/development/core/getting-started.html +31 -8
  183. nautobot/project-static/docs/development/core/homepage.html +31 -8
  184. nautobot/project-static/docs/development/core/index.html +31 -8
  185. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +31 -8
  186. nautobot/project-static/docs/development/core/model-checklist.html +31 -8
  187. nautobot/project-static/docs/development/core/model-features.html +31 -8
  188. nautobot/project-static/docs/development/core/natural-keys.html +31 -8
  189. nautobot/project-static/docs/development/core/navigation-menu.html +31 -8
  190. nautobot/project-static/docs/development/core/release-checklist.html +31 -8
  191. nautobot/project-static/docs/development/core/role-internals.html +31 -8
  192. nautobot/project-static/docs/development/core/settings.html +31 -8
  193. nautobot/project-static/docs/development/core/style-guide.html +31 -8
  194. nautobot/project-static/docs/development/core/templates.html +31 -8
  195. nautobot/project-static/docs/development/core/testing.html +31 -8
  196. nautobot/project-static/docs/development/core/ui-component-framework.html +31 -8
  197. nautobot/project-static/docs/development/core/user-preferences.html +31 -8
  198. nautobot/project-static/docs/development/index.html +31 -8
  199. nautobot/project-static/docs/development/jobs/getting-started.html +35 -8
  200. nautobot/project-static/docs/development/jobs/index.html +31 -8
  201. nautobot/project-static/docs/development/jobs/installation.html +31 -8
  202. nautobot/project-static/docs/development/jobs/job-extensions.html +31 -8
  203. nautobot/project-static/docs/development/jobs/job-logging.html +31 -8
  204. nautobot/project-static/docs/development/jobs/job-patterns.html +31 -8
  205. nautobot/project-static/docs/development/jobs/job-structure.html +31 -8
  206. nautobot/project-static/docs/development/jobs/migration/from-v1.html +31 -8
  207. nautobot/project-static/docs/development/jobs/testing.html +31 -8
  208. nautobot/project-static/docs/index.html +31 -8
  209. nautobot/project-static/docs/insert-analytics.sh +36 -0
  210. nautobot/project-static/docs/objects.inv +0 -0
  211. nautobot/project-static/docs/overview/application_stack.html +31 -8
  212. nautobot/project-static/docs/overview/design_philosophy.html +31 -8
  213. nautobot/project-static/docs/release-notes/index.html +31 -8
  214. nautobot/project-static/docs/release-notes/version-1.0.html +31 -8
  215. nautobot/project-static/docs/release-notes/version-1.1.html +31 -8
  216. nautobot/project-static/docs/release-notes/version-1.2.html +31 -8
  217. nautobot/project-static/docs/release-notes/version-1.3.html +31 -8
  218. nautobot/project-static/docs/release-notes/version-1.4.html +31 -8
  219. nautobot/project-static/docs/release-notes/version-1.5.html +31 -8
  220. nautobot/project-static/docs/release-notes/version-1.6.html +31 -8
  221. nautobot/project-static/docs/release-notes/version-2.0.html +31 -8
  222. nautobot/project-static/docs/release-notes/version-2.1.html +31 -8
  223. nautobot/project-static/docs/release-notes/version-2.2.html +31 -8
  224. nautobot/project-static/docs/release-notes/version-2.3.html +31 -8
  225. nautobot/project-static/docs/release-notes/version-2.4.html +252 -8
  226. nautobot/project-static/docs/search/search_index.json +1 -1
  227. nautobot/project-static/docs/sitemap.xml +302 -298
  228. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  229. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +31 -8
  230. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +31 -8
  231. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +31 -8
  232. nautobot/project-static/docs/user-guide/administration/configuration/index.html +31 -8
  233. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +31 -8
  234. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +31 -8
  235. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +31 -8
  236. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +31 -8
  237. nautobot/project-static/docs/user-guide/administration/guides/docker.html +31 -8
  238. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +31 -8
  239. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +31 -8
  240. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +31 -8
  241. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +31 -8
  242. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +31 -8
  243. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +31 -8
  244. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +31 -8
  245. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +31 -8
  246. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +31 -8
  247. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +31 -8
  248. nautobot/project-static/docs/user-guide/administration/installation/index.html +31 -8
  249. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +31 -8
  250. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +31 -8
  251. nautobot/project-static/docs/user-guide/administration/installation/services.html +31 -8
  252. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +31 -8
  253. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +31 -8
  254. nautobot/project-static/docs/user-guide/administration/security/index.html +31 -8
  255. nautobot/project-static/docs/user-guide/administration/security/notices.html +31 -8
  256. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +31 -8
  257. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +31 -8
  258. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +31 -8
  259. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +31 -8
  260. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +31 -8
  261. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +31 -8
  262. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +31 -8
  263. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +31 -8
  264. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +31 -8
  265. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +31 -8
  266. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +31 -8
  267. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +31 -8
  268. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +31 -8
  269. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +31 -8
  270. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +31 -8
  271. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +31 -8
  272. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +31 -8
  273. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +31 -8
  274. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +31 -8
  275. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +31 -8
  276. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +31 -8
  277. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +31 -8
  278. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +31 -8
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +31 -8
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +31 -8
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +31 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +31 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +31 -8
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +31 -8
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +31 -8
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +31 -8
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +31 -8
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +31 -8
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +31 -8
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +43 -20
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +31 -8
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +31 -8
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +31 -8
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +31 -8
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +31 -8
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +31 -8
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +31 -8
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +31 -8
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +31 -8
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +31 -8
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +35 -8
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +35 -8
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +35 -8
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +10261 -0
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -11
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +31 -8
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +31 -8
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +31 -8
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +31 -8
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +31 -8
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +31 -8
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +31 -8
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +31 -8
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +31 -8
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +31 -8
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +31 -8
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +31 -8
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +31 -8
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +31 -8
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +31 -8
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +31 -8
  322. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +31 -8
  323. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +31 -8
  324. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +31 -8
  325. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +31 -8
  326. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +31 -8
  327. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +31 -8
  328. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +31 -8
  329. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +31 -8
  330. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +31 -8
  331. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +31 -8
  332. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +31 -8
  333. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +31 -8
  334. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +31 -8
  335. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +31 -8
  336. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +31 -8
  337. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +31 -8
  338. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +31 -8
  339. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +31 -8
  340. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +31 -8
  341. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +31 -8
  342. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +31 -8
  343. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +31 -8
  344. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +31 -8
  345. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +31 -8
  346. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +31 -8
  347. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +31 -8
  348. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +31 -8
  349. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +31 -8
  350. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +31 -8
  351. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +31 -8
  352. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +31 -8
  353. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +31 -8
  354. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +31 -8
  355. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +31 -8
  356. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +31 -8
  357. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +31 -8
  358. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +41 -15
  359. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +31 -8
  360. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +31 -8
  361. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +31 -8
  362. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +31 -8
  363. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +31 -8
  364. nautobot/project-static/docs/user-guide/index.html +31 -8
  365. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +31 -8
  366. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +31 -8
  367. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +31 -8
  368. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +31 -8
  369. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +31 -8
  370. nautobot/project-static/docs/user-guide/platform-functionality/events.html +31 -8
  371. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +31 -8
  372. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +31 -8
  373. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +37 -9
  374. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +31 -8
  375. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +31 -8
  376. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +31 -8
  377. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +31 -8
  378. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +31 -8
  379. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +31 -8
  380. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +31 -8
  381. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +31 -8
  382. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +31 -8
  383. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +31 -8
  384. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +31 -8
  385. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +31 -8
  386. nautobot/project-static/docs/user-guide/platform-functionality/note.html +31 -8
  387. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +31 -8
  388. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +31 -8
  389. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +31 -8
  390. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +31 -8
  391. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +31 -8
  392. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +31 -8
  393. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +31 -8
  394. nautobot/project-static/docs/user-guide/platform-functionality/role.html +31 -8
  395. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +31 -8
  396. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +31 -8
  397. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +31 -8
  398. nautobot/project-static/docs/user-guide/platform-functionality/status.html +31 -8
  399. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +31 -8
  400. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +31 -8
  401. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +31 -8
  402. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +31 -8
  403. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +31 -8
  404. nautobot/tenancy/tables.py +2 -0
  405. nautobot/virtualization/tests/test_views.py +1 -1
  406. nautobot/wireless/forms.py +0 -1
  407. nautobot/wireless/models.py +1 -1
  408. nautobot/wireless/tables.py +7 -0
  409. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/METADATA +4 -4
  410. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/RECORD +416 -401
  411. /nautobot/dcim/templates/dcim/{platform_edit.html → platform_create.html} +0 -0
  412. /nautobot/extras/test_jobs/{pass.py → pass_job.py} +0 -0
  413. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/LICENSE.txt +0 -0
  414. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/NOTICE +0 -0
  415. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/WHEEL +0 -0
  416. {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/entry_points.txt +0 -0
nautobot/dcim/views.py CHANGED
@@ -16,6 +16,7 @@ from django.forms import (
16
16
  MultipleHiddenInput,
17
17
  )
18
18
  from django.shortcuts import get_object_or_404, HttpResponse, redirect, render
19
+ from django.template.loader import render_to_string
19
20
  from django.urls import reverse
20
21
  from django.utils.encoding import iri_to_uri
21
22
  from django.utils.functional import cached_property
@@ -71,6 +72,7 @@ from nautobot.wireless.models import (
71
72
  from nautobot.wireless.tables import (
72
73
  ControllerManagedDeviceGroupRadioProfileAssignmentTable,
73
74
  ControllerManagedDeviceGroupWirelessNetworkAssignmentTable,
75
+ DeviceGroupWirelessNetworkTable,
74
76
  RadioProfileTable,
75
77
  )
76
78
 
@@ -106,6 +108,7 @@ from .models import (
106
108
  Module,
107
109
  ModuleBay,
108
110
  ModuleBayTemplate,
111
+ ModuleFamily,
109
112
  ModuleType,
110
113
  PathEndpoint,
111
114
  Platform,
@@ -251,21 +254,22 @@ class LocationTypeUIViewSet(NautobotUIViewSet):
251
254
  #
252
255
 
253
256
 
254
- class LocationListView(generic.ObjectListView):
255
- queryset = Location.objects.all()
256
- filterset = filters.LocationFilterSet
257
- filterset_form = forms.LocationFilterForm
258
- table = tables.LocationTable
259
-
260
-
261
- class LocationView(generic.ObjectView):
257
+ class LocationUIViewSet(NautobotUIViewSet):
262
258
  # We aren't accessing tree fields anywhere so this is safe (note that `parent` itself is a normal foreign
263
259
  # key, not a tree field). If we ever do access tree fields, this will perform worse, because django will
264
260
  # automatically issue a second query (similar to behavior for
265
261
  # https://docs.djangoproject.com/en/3.2/ref/models/querysets/#django.db.models.query.QuerySet.only)
266
- queryset = Location.objects.without_tree_fields().all()
262
+ queryset = Location.objects.without_tree_fields().select_related("location_type", "parent", "tenant")
263
+ filterset_class = filters.LocationFilterSet
264
+ filterset_form_class = forms.LocationFilterForm
265
+ table_class = tables.LocationTable
266
+ form_class = forms.LocationForm
267
+ bulk_update_form_class = forms.LocationBulkEditForm
268
+ serializer_class = serializers.LocationSerializer
267
269
 
268
270
  def get_extra_context(self, request, instance):
271
+ if instance is None:
272
+ return super().get_extra_context(request, instance)
269
273
  related_locations = (
270
274
  instance.descendants(include_self=True).restrict(request.user, "view").values_list("pk", flat=True)
271
275
  )
@@ -305,7 +309,6 @@ class LocationView(generic.ObjectView):
305
309
  )
306
310
 
307
311
  children_table = tables.LocationTable(children, hide_hierarchy_ui=True)
308
-
309
312
  paginate = {
310
313
  "paginator_class": EnhancedPaginator,
311
314
  "per_page": get_paginate_count(request),
@@ -323,34 +326,6 @@ class LocationView(generic.ObjectView):
323
326
  }
324
327
 
325
328
 
326
- class LocationEditView(generic.ObjectEditView):
327
- queryset = Location.objects.all()
328
- model_form = forms.LocationForm
329
- template_name = "dcim/location_edit.html"
330
-
331
-
332
- class LocationDeleteView(generic.ObjectDeleteView):
333
- queryset = Location.objects.all()
334
-
335
-
336
- class LocationBulkEditView(generic.BulkEditView):
337
- queryset = Location.objects.select_related("location_type", "parent", "tenant")
338
- filterset = filters.LocationFilterSet
339
- table = tables.LocationTable
340
- form = forms.LocationBulkEditForm
341
-
342
-
343
- class LocationBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
344
- queryset = Location.objects.all()
345
- table = tables.LocationTable
346
-
347
-
348
- class LocationBulkDeleteView(generic.BulkDeleteView):
349
- queryset = Location.objects.select_related("location_type", "parent", "tenant")
350
- filterset = filters.LocationFilterSet
351
- table = tables.LocationTable
352
-
353
-
354
329
  class MigrateLocationDataToContactView(generic.ObjectEditView):
355
330
  queryset = Location.objects.all()
356
331
  model_form = LocationMigrateDataToContactForm
@@ -1623,7 +1598,14 @@ class ModuleBayTemplateUIViewSet(
1623
1598
  return parent.display
1624
1599
  return ""
1625
1600
 
1626
- @action(detail=False, methods=["GET", "POST"], url_path="rename", url_name="bulk_rename")
1601
+ @action(
1602
+ detail=False,
1603
+ methods=["GET", "POST"],
1604
+ url_path="rename",
1605
+ url_name="bulk_rename",
1606
+ custom_view_base_action="change",
1607
+ custom_view_additional_permissions=["dcim.change_modulebaytemplate"],
1608
+ )
1627
1609
  def bulk_rename(self, request, *args, **kwargs):
1628
1610
  return self._bulk_rename(request, *args, **kwargs)
1629
1611
 
@@ -2393,35 +2375,6 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2393
2375
  table_class = tables.ModuleTable
2394
2376
  component_model = None
2395
2377
 
2396
- def get_action(self):
2397
- if self.component_model:
2398
- method = self.request.method.lower()
2399
- if method == "get":
2400
- return "view"
2401
- else:
2402
- return "change"
2403
-
2404
- return super().get_action()
2405
-
2406
- def get_required_permission(self):
2407
- # TODO: standardize a pattern for permissions enforcement on custom actions
2408
- if self.component_model:
2409
- model = self.component_model
2410
- method = self.request.method.lower()
2411
- if method == "get":
2412
- component_action = "view"
2413
- permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.view_module"]
2414
- elif self.action.startswith("bulk_add"):
2415
- component_action = "add"
2416
- permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.change_module"]
2417
- else:
2418
- component_action = "change"
2419
- permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.change_module"]
2420
-
2421
- return permissions
2422
-
2423
- return super().get_required_permission()
2424
-
2425
2378
  def get_extra_context(self, request, instance):
2426
2379
  context = super().get_extra_context(request, instance)
2427
2380
  if instance:
@@ -2448,7 +2401,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2448
2401
 
2449
2402
  return active_parent_tab
2450
2403
 
2451
- @action(detail=True, url_path="console-ports", component_model=ConsolePort)
2404
+ @action(
2405
+ detail=True,
2406
+ url_path="console-ports",
2407
+ component_model=ConsolePort,
2408
+ custom_view_base_action="view",
2409
+ custom_view_additional_permissions=["dcim.view_consoleport"],
2410
+ )
2452
2411
  def consoleports(self, request, *args, **kwargs):
2453
2412
  instance = self.get_object()
2454
2413
  consoleports = (
@@ -2467,7 +2426,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2467
2426
  }
2468
2427
  )
2469
2428
 
2470
- @action(detail=True, url_path="console-server-ports", component_model=ConsoleServerPort)
2429
+ @action(
2430
+ detail=True,
2431
+ url_path="console-server-ports",
2432
+ component_model=ConsoleServerPort,
2433
+ custom_view_base_action="view",
2434
+ custom_view_additional_permissions=["dcim.view_consoleserverport"],
2435
+ )
2471
2436
  def consoleserverports(self, request, *args, **kwargs):
2472
2437
  instance = self.get_object()
2473
2438
  consoleserverports = (
@@ -2490,7 +2455,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2490
2455
  }
2491
2456
  )
2492
2457
 
2493
- @action(detail=True, url_path="power-ports", component_model=PowerPort)
2458
+ @action(
2459
+ detail=True,
2460
+ url_path="power-ports",
2461
+ component_model=PowerPort,
2462
+ custom_view_base_action="view",
2463
+ custom_view_additional_permissions=["dcim.view_powerport"],
2464
+ )
2494
2465
  def powerports(self, request, *args, **kwargs):
2495
2466
  instance = self.get_object()
2496
2467
  powerports = (
@@ -2511,7 +2482,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2511
2482
  }
2512
2483
  )
2513
2484
 
2514
- @action(detail=True, url_path="power-outlets", component_model=PowerOutlet)
2485
+ @action(
2486
+ detail=True,
2487
+ url_path="power-outlets",
2488
+ component_model=PowerOutlet,
2489
+ custom_view_base_action="view",
2490
+ custom_view_additional_permissions=["dcim.view_poweroutlet"],
2491
+ )
2515
2492
  def poweroutlets(self, request, *args, **kwargs):
2516
2493
  instance = self.get_object()
2517
2494
  poweroutlets = (
@@ -2532,7 +2509,12 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2532
2509
  }
2533
2510
  )
2534
2511
 
2535
- @action(detail=True, component_model=Interface)
2512
+ @action(
2513
+ detail=True,
2514
+ component_model=Interface,
2515
+ custom_view_base_action="view",
2516
+ custom_view_additional_permissions=["dcim.view_interface"],
2517
+ )
2536
2518
  def interfaces(self, request, *args, **kwargs):
2537
2519
  instance = self.get_object()
2538
2520
  interfaces = (
@@ -2558,7 +2540,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2558
2540
  }
2559
2541
  )
2560
2542
 
2561
- @action(detail=True, url_path="front-ports", component_model=FrontPort)
2543
+ @action(
2544
+ detail=True,
2545
+ url_path="front-ports",
2546
+ component_model=FrontPort,
2547
+ custom_view_base_action="view",
2548
+ custom_view_additional_permissions=["dcim.view_frontport"],
2549
+ )
2562
2550
  def frontports(self, request, *args, **kwargs):
2563
2551
  instance = self.get_object()
2564
2552
  frontports = instance.front_ports.restrict(request.user, "view").select_related("cable", "rear_port")
@@ -2575,7 +2563,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2575
2563
  },
2576
2564
  )
2577
2565
 
2578
- @action(detail=True, url_path="rear-ports", component_model=RearPort)
2566
+ @action(
2567
+ detail=True,
2568
+ url_path="rear-ports",
2569
+ component_model=RearPort,
2570
+ custom_view_base_action="view",
2571
+ custom_view_additional_permissions=["dcim.view_rearport"],
2572
+ )
2579
2573
  def rearports(self, request, *args, **kwargs):
2580
2574
  instance = self.get_object()
2581
2575
  rearports = instance.rear_ports.restrict(request.user, "view").select_related("cable")
@@ -2592,7 +2586,13 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2592
2586
  }
2593
2587
  )
2594
2588
 
2595
- @action(detail=True, url_path="module-bays", component_model=ModuleBay)
2589
+ @action(
2590
+ detail=True,
2591
+ url_path="module-bays",
2592
+ component_model=ModuleBay,
2593
+ custom_view_base_action="view",
2594
+ custom_view_additional_permissions=["dcim.view_modulebay"],
2595
+ )
2596
2596
  def modulebays(self, request, *args, **kwargs):
2597
2597
  instance = self.get_object()
2598
2598
  modulebays = instance.module_bays.restrict(request.user, "view").prefetch_related(
@@ -2615,6 +2615,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2615
2615
  url_path="console-ports/add",
2616
2616
  url_name="bulk_add_consoleport",
2617
2617
  component_model=ConsolePort,
2618
+ custom_view_base_action="change",
2619
+ custom_view_additional_permissions=["dcim.add_consoleport"],
2618
2620
  )
2619
2621
  def bulk_add_consoleport(self, request, *args, **kwargs):
2620
2622
  return self._bulk_component_create(
@@ -2629,6 +2631,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2629
2631
  url_path="console-server-ports/add",
2630
2632
  url_name="bulk_add_consoleserverport",
2631
2633
  component_model=ConsoleServerPort,
2634
+ custom_view_base_action="change",
2635
+ custom_view_additional_permissions=["dcim.add_consoleserverport"],
2632
2636
  )
2633
2637
  def bulk_add_consoleserverport(self, request, *args, **kwargs):
2634
2638
  return self._bulk_component_create(
@@ -2643,6 +2647,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2643
2647
  url_path="power-ports/add",
2644
2648
  url_name="bulk_add_powerport",
2645
2649
  component_model=PowerPort,
2650
+ custom_view_base_action="change",
2651
+ custom_view_additional_permissions=["dcim.add_powerport"],
2646
2652
  )
2647
2653
  def bulk_add_powerport(self, request, *args, **kwargs):
2648
2654
  return self._bulk_component_create(
@@ -2657,6 +2663,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2657
2663
  url_path="power-outlets/add",
2658
2664
  url_name="bulk_add_poweroutlet",
2659
2665
  component_model=PowerOutlet,
2666
+ custom_view_base_action="change",
2667
+ custom_view_additional_permissions=["dcim.add_poweroutlet"],
2660
2668
  )
2661
2669
  def bulk_add_poweroutlet(self, request, *args, **kwargs):
2662
2670
  return self._bulk_component_create(
@@ -2671,6 +2679,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2671
2679
  url_path="interfaces/add",
2672
2680
  url_name="bulk_add_interface",
2673
2681
  component_model=Interface,
2682
+ custom_view_base_action="change",
2683
+ custom_view_additional_permissions=["dcim.add_interface"],
2674
2684
  )
2675
2685
  def bulk_add_interface(self, request, *args, **kwargs):
2676
2686
  return self._bulk_component_create(
@@ -2685,6 +2695,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2685
2695
  url_path="rear-ports/add",
2686
2696
  url_name="bulk_add_rearport",
2687
2697
  component_model=RearPort,
2698
+ custom_view_base_action="change",
2699
+ custom_view_additional_permissions=["dcim.add_rearport"],
2688
2700
  )
2689
2701
  def bulk_add_rearport(self, request, *args, **kwargs):
2690
2702
  return self._bulk_component_create(
@@ -2699,6 +2711,8 @@ class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
2699
2711
  url_path="module-bays/add",
2700
2712
  url_name="bulk_add_modulebay",
2701
2713
  component_model=ModuleBay,
2714
+ custom_view_base_action="change",
2715
+ custom_view_additional_permissions=["dcim.add_modulebay"],
2702
2716
  )
2703
2717
  def bulk_add_modulebay(self, request, *args, **kwargs):
2704
2718
  return self._bulk_component_create(
@@ -3394,7 +3408,6 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
3394
3408
  serializer_class = serializers.ModuleBaySerializer
3395
3409
  table_class = tables.ModuleBayTable
3396
3410
  create_template_name = "dcim/device_component_add.html"
3397
-
3398
3411
  object_detail_content = object_detail.ObjectDetailContent(
3399
3412
  panels=(
3400
3413
  object_detail.ObjectFieldsPanel(
@@ -3439,7 +3452,14 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
3439
3452
  return parent.display
3440
3453
  return ""
3441
3454
 
3442
- @action(detail=False, methods=["GET", "POST"], url_path="rename", url_name="bulk_rename")
3455
+ @action(
3456
+ detail=False,
3457
+ methods=["GET", "POST"],
3458
+ url_path="rename",
3459
+ url_name="bulk_rename",
3460
+ custom_view_base_action="change",
3461
+ custom_view_additional_permissions=["dcim.change_modulebay"],
3462
+ )
3443
3463
  def bulk_rename(self, request, *args, **kwargs):
3444
3464
  return self._bulk_rename(request, *args, **kwargs)
3445
3465
 
@@ -4159,6 +4179,28 @@ class PowerPanelUIViewSet(NautobotUIViewSet):
4159
4179
  #
4160
4180
  # Power feeds
4161
4181
  #
4182
+
4183
+
4184
+ class CustomPowerFeedKeyValueTablePanel(object_detail.KeyValueTablePanel):
4185
+ """Custom panel to render PowerFeed utilization graph cleanly."""
4186
+
4187
+ def render_value(self, key, value, context):
4188
+ if key == "Utilization (Allocated)":
4189
+ if not value or not isinstance(value, tuple) or len(value) != 2:
4190
+ return helpers.placeholder(None)
4191
+ allocated, available = value
4192
+ if available <= 0:
4193
+ return f"{allocated}VA / {available}VA"
4194
+ graph_html = render_to_string(
4195
+ "utilities/templatetags/utilization_graph.html",
4196
+ helpers.utilization_graph_raw_data(allocated, available),
4197
+ )
4198
+ return format_html("{}VA / {}VA {}", allocated, available, graph_html)
4199
+
4200
+ # Fall back to default behavior for everything else
4201
+ return super().render_value(key, value, context)
4202
+
4203
+
4162
4204
  class PowerFeedUIViewSet(NautobotUIViewSet):
4163
4205
  bulk_update_form_class = forms.PowerFeedBulkEditForm
4164
4206
  filterset_class = filters.PowerFeedFilterSet
@@ -4168,6 +4210,137 @@ class PowerFeedUIViewSet(NautobotUIViewSet):
4168
4210
  serializer_class = serializers.PowerFeedSerializer
4169
4211
  table_class = tables.PowerFeedTable
4170
4212
 
4213
+ object_detail_content = object_detail.ObjectDetailContent(
4214
+ panels=(
4215
+ CustomPowerFeedKeyValueTablePanel(
4216
+ section=SectionChoices.LEFT_HALF,
4217
+ weight=100,
4218
+ label="Power Feed",
4219
+ context_data_key="powerfeed_data",
4220
+ ),
4221
+ object_detail.ObjectFieldsPanel(
4222
+ section=SectionChoices.LEFT_HALF,
4223
+ weight=200,
4224
+ label="Electrical Characteristics",
4225
+ fields=["supply", "voltage", "amperage", "phase", "max_utilization"],
4226
+ value_transforms={
4227
+ "voltage": [lambda v: f"{v}V" if v is not None else helpers.placeholder(v)],
4228
+ "amperage": [lambda a: f"{a}A" if a is not None else helpers.placeholder(a)],
4229
+ "max_utilization": [lambda p: f"{p}%" if p is not None else helpers.placeholder(p)],
4230
+ },
4231
+ ),
4232
+ object_detail.KeyValueTablePanel(
4233
+ section=SectionChoices.RIGHT_HALF,
4234
+ weight=300,
4235
+ label="Connection",
4236
+ context_data_key="connection_data",
4237
+ ),
4238
+ )
4239
+ )
4240
+
4241
+ def get_extra_context(self, request, instance):
4242
+ context = super().get_extra_context(request, instance)
4243
+ if not instance or self.action != "retrieve":
4244
+ return context
4245
+
4246
+ context["powerfeed_data"] = {
4247
+ "Power Panel": instance.power_panel,
4248
+ "Rack": instance.rack,
4249
+ "Type": self._get_type_html(instance), # Render Type with HTML label
4250
+ "Status": instance.status,
4251
+ "Connected Device": self._get_connected_device_html(instance),
4252
+ "Utilization (Allocated)": self._get_utilization_data(instance),
4253
+ }
4254
+
4255
+ context["connection_data"] = self._get_connection_data(request, instance)
4256
+ return context
4257
+
4258
+ def _get_type_html(self, instance):
4259
+ """
4260
+ Render the PowerFeed type as a label with the appropriate CSS class.
4261
+ """
4262
+
4263
+ type_class = instance.get_type_class()
4264
+ return format_html('<span class="label label-{}">{}</span>', type_class, instance.get_type_display())
4265
+
4266
+ def _get_connected_device_html(self, instance):
4267
+ endpoint = getattr(instance, "connected_endpoint", None)
4268
+ if endpoint and endpoint.parent:
4269
+ parent = helpers.hyperlinked_object(endpoint.parent)
4270
+ return format_html("{} ({})", parent, endpoint)
4271
+ return None
4272
+
4273
+ def _get_utilization_data(self, instance):
4274
+ endpoint = getattr(instance, "connected_endpoint", None)
4275
+ if not endpoint or not hasattr(endpoint, "get_power_draw"):
4276
+ return None
4277
+ utilization = endpoint.get_power_draw()
4278
+ if not utilization or "allocated" not in utilization:
4279
+ return None
4280
+ allocated = utilization["allocated"]
4281
+ available = instance.available_power or 0
4282
+ return (allocated, available)
4283
+
4284
+ def _get_connection_data(self, request, instance):
4285
+ if not instance:
4286
+ return {}
4287
+
4288
+ if instance.cable:
4289
+ trace_url = reverse("dcim:powerfeed_trace", kwargs={"pk": instance.pk})
4290
+ cable_html = format_html(
4291
+ '{} <a href="{}" class="btn btn-primary btn-xs" title="Trace">'
4292
+ '<i class="mdi mdi-transit-connection-variant"></i></a>',
4293
+ helpers.hyperlinked_object(instance.cable),
4294
+ trace_url,
4295
+ )
4296
+
4297
+ endpoint = getattr(instance, "connected_endpoint", None)
4298
+ endpoint_data = {}
4299
+
4300
+ if endpoint:
4301
+ endpoint_obj = getattr(endpoint, "device", None) or getattr(endpoint, "module", None)
4302
+ # Removed the unused 'path' variable
4303
+ endpoint_data = {
4304
+ "Device" if getattr(endpoint, "device", None) else "Module": endpoint_obj,
4305
+ "Power Port": endpoint,
4306
+ "Type": endpoint.get_type_display() if hasattr(endpoint, "get_type_display") else None,
4307
+ "Description": endpoint.description,
4308
+ "Path Status": self._get_path_status_html(instance), # Render Path Status dynamically
4309
+ }
4310
+
4311
+ return {
4312
+ "Cable": cable_html,
4313
+ **endpoint_data,
4314
+ }
4315
+
4316
+ if request.user.has_perm("dcim.add_cable"):
4317
+ connect_url = (
4318
+ reverse(
4319
+ "dcim:powerfeed_connect",
4320
+ kwargs={"termination_a_id": instance.pk, "termination_b_type": "power-port"},
4321
+ )
4322
+ + f"?return_url={instance.get_absolute_url()}"
4323
+ )
4324
+ connect_link = format_html(
4325
+ '<a href="{}" class="btn btn-primary btn-sm pull-right">'
4326
+ '<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect</a>',
4327
+ connect_url,
4328
+ )
4329
+ return {"Connection": format_html("Not connected {}", connect_link)}
4330
+
4331
+ return {"Connection": "Not connected"}
4332
+
4333
+ def _get_path_status_html(self, instance):
4334
+ """
4335
+ Render the Path Status as a label based on the path status (active or not).
4336
+ """
4337
+ path_status = (
4338
+ '<span class="label label-success">Reachable</span>'
4339
+ if getattr(instance, "path", None) and instance.path.is_active
4340
+ else '<span class="label label-danger">Not Reachable</span>'
4341
+ )
4342
+ return format_html(path_status) # Safely render HTML
4343
+
4171
4344
 
4172
4345
  class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
4173
4346
  bulk_update_form_class = forms.DeviceRedundancyGroupBulkEditForm
@@ -4218,38 +4391,35 @@ class InterfaceRedundancyGroupUIViewSet(NautobotUIViewSet):
4218
4391
  table_class = tables.InterfaceRedundancyGroupTable
4219
4392
  lookup_field = "pk"
4220
4393
 
4221
- def get_extra_context(self, request, instance):
4222
- """Return additional panels for display."""
4223
- context = super().get_extra_context(request, instance)
4224
- if instance and self.action == "retrieve":
4225
- interface_table = self._get_interface_redundancy_groups_table(request, instance)
4226
- context["interface_table"] = interface_table
4227
- return context
4228
-
4229
- def _get_interface_redundancy_groups_table(self, request, instance):
4230
- """Return a table of assigned Interfaces."""
4231
- queryset = instance.interface_redundancy_group_associations.restrict(request.user)
4232
- queryset = queryset.prefetch_related("interface")
4233
- queryset = queryset.order_by("priority")
4234
- column_sequence = (
4235
- "interface__device",
4236
- "interface",
4237
- "priority",
4238
- "interface__status",
4239
- "interface__enabled",
4240
- "interface__ip_addresses",
4241
- "interface__type",
4242
- "interface__description",
4243
- "interface__label",
4244
- )
4245
- table = tables.InterfaceRedundancyGroupAssociationTable(
4246
- data=queryset,
4247
- sequence=column_sequence,
4248
- orderable=False,
4249
- )
4250
- for column_name in column_sequence:
4251
- table.columns.show(column_name)
4252
- return table
4394
+ object_detail_content = object_detail.ObjectDetailContent(
4395
+ panels=(
4396
+ object_detail.ObjectFieldsPanel(
4397
+ weight=100,
4398
+ section=SectionChoices.LEFT_HALF,
4399
+ fields="__all__",
4400
+ ),
4401
+ object_detail.ObjectsTablePanel(
4402
+ weight=200,
4403
+ section=SectionChoices.FULL_WIDTH,
4404
+ table_class=tables.InterfaceRedundancyGroupAssociationTable,
4405
+ table_attribute="interface_redundancy_group_associations",
4406
+ prefetch_related_fields=["interface"],
4407
+ order_by_fields=["priority"],
4408
+ table_title="Interfaces",
4409
+ related_field_name="interface_redundancy_group",
4410
+ include_columns=[
4411
+ "interface__device",
4412
+ "interface",
4413
+ "interface__status",
4414
+ "interface__enabled",
4415
+ "interface__ip_addresses",
4416
+ "interface__type",
4417
+ "interface__description",
4418
+ "interface__label",
4419
+ ],
4420
+ ),
4421
+ ),
4422
+ )
4253
4423
 
4254
4424
 
4255
4425
  class InterfaceRedundancyGroupAssociationUIViewSet(ObjectEditViewMixin, ObjectDestroyViewMixin):
@@ -4259,6 +4429,51 @@ class InterfaceRedundancyGroupAssociationUIViewSet(ObjectEditViewMixin, ObjectDe
4259
4429
  lookup_field = "pk"
4260
4430
 
4261
4431
 
4432
+ class ModuleFamilyUIViewSet(NautobotUIViewSet):
4433
+ """ViewSet for the ModuleFamily model."""
4434
+
4435
+ filterset_class = filters.ModuleFamilyFilterSet
4436
+ filterset_form_class = forms.ModuleFamilyFilterForm
4437
+ form_class = forms.ModuleFamilyForm
4438
+ bulk_update_form_class = forms.ModuleFamilyBulkEditForm
4439
+ queryset = ModuleFamily.objects.all()
4440
+ serializer_class = serializers.ModuleFamilySerializer
4441
+ table_class = tables.ModuleFamilyTable
4442
+ lookup_field = "pk"
4443
+
4444
+ def get_extra_context(self, request, instance):
4445
+ context = super().get_extra_context(request, instance)
4446
+ if not instance:
4447
+ return context
4448
+
4449
+ if self.action == "retrieve":
4450
+ module_types = (
4451
+ ModuleType.objects.restrict(request.user, "view")
4452
+ .filter(module_family=instance)
4453
+ .select_related("manufacturer")
4454
+ )
4455
+ module_type_table = tables.ModuleTypeTable(module_types, orderable=False)
4456
+
4457
+ module_bays = (
4458
+ ModuleBay.objects.restrict(request.user, "view")
4459
+ .filter(module_family=instance)
4460
+ .select_related("parent_device", "parent_module")
4461
+ )
4462
+ module_bay_table = tables.ModuleBayTable(module_bays, orderable=False)
4463
+
4464
+ paginate = {
4465
+ "paginator_class": EnhancedPaginator,
4466
+ "per_page": get_paginate_count(request),
4467
+ }
4468
+ RequestConfig(request, paginate).configure(module_type_table)
4469
+ RequestConfig(request, paginate).configure(module_bay_table)
4470
+
4471
+ context["module_type_table"] = module_type_table
4472
+ context["module_bay_table"] = module_bay_table
4473
+
4474
+ return context
4475
+
4476
+
4262
4477
  class DeviceFamilyUIViewSet(NautobotUIViewSet):
4263
4478
  filterset_class = filters.DeviceFamilyFilterSet
4264
4479
  filterset_form_class = forms.DeviceFamilyFilterForm
@@ -4308,7 +4523,6 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4308
4523
  queryset = SoftwareImageFile.objects.all()
4309
4524
  serializer_class = serializers.SoftwareImageFileSerializer
4310
4525
  table_class = tables.SoftwareImageFileTable
4311
-
4312
4526
  object_detail_content = object_detail.ObjectDetailContent(
4313
4527
  panels=(
4314
4528
  object_detail.ObjectFieldsPanel(
@@ -4392,19 +4606,42 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4392
4606
  ),
4393
4607
  )
4394
4608
 
4395
- @action(detail=True, url_path="device-types", url_name="device_types")
4609
+ @action(
4610
+ detail=True,
4611
+ url_path="device-types",
4612
+ url_name="device_types",
4613
+ custom_view_base_action="view",
4614
+ custom_view_additional_permissions=["dcim.view_devicetype"],
4615
+ )
4396
4616
  def device_types(self, request, *args, **kwargs):
4397
4617
  return Response({})
4398
4618
 
4399
- @action(detail=True, url_path="devices")
4619
+ @action(
4620
+ detail=True,
4621
+ url_path="devices",
4622
+ custom_view_base_action="view",
4623
+ custom_view_additional_permissions=["dcim.view_device"],
4624
+ )
4400
4625
  def devices(self, request, *args, **kwargs):
4401
4626
  return Response({})
4402
4627
 
4403
- @action(detail=True, url_path="inventory-items", url_name="inventory_items")
4628
+ @action(
4629
+ detail=True,
4630
+ url_path="inventory-items",
4631
+ url_name="inventory_items",
4632
+ custom_view_base_action="view",
4633
+ custom_view_additional_permissions=["dcim.view_inventoryitem"],
4634
+ )
4404
4635
  def inventory_items(self, request, *args, **kwargs):
4405
4636
  return Response({})
4406
4637
 
4407
- @action(detail=True, url_path="virtual-machines", url_name="virtual_machines")
4638
+ @action(
4639
+ detail=True,
4640
+ url_path="virtual-machines",
4641
+ url_name="virtual_machines",
4642
+ custom_view_base_action="view",
4643
+ custom_view_additional_permissions=["virtualization.view_virtualmachine"],
4644
+ )
4408
4645
  def virtual_machines(self, request, *args, **kwargs):
4409
4646
  return Response({})
4410
4647
 
@@ -4470,7 +4707,14 @@ class ControllerUIViewSet(NautobotUIViewSet):
4470
4707
 
4471
4708
  return context
4472
4709
 
4473
- @action(detail=True, url_path="wireless-networks", url_name="wirelessnetworks", methods=["get"])
4710
+ @action(
4711
+ detail=True,
4712
+ url_path="wireless-networks",
4713
+ url_name="wirelessnetworks",
4714
+ methods=["get"],
4715
+ custom_view_base_action="view",
4716
+ custom_view_additional_permissions=["wireless.view_controllermanageddevicegroupwirelessnetworkassignment"],
4717
+ )
4474
4718
  def wirelessnetworks(self, request, *args, **kwargs):
4475
4719
  instance = self.get_object()
4476
4720
  controller_managed_device_groups = instance.controller_managed_device_groups.restrict(
@@ -4495,10 +4739,6 @@ class ControllerUIViewSet(NautobotUIViewSet):
4495
4739
  }
4496
4740
  )
4497
4741
 
4498
- def get_action(self):
4499
- "Treat Wireless Networks as the same detail view for permission purposes."
4500
- return "view" if self.action == "wirelessnetworks" else super().get_action()
4501
-
4502
4742
 
4503
4743
  class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
4504
4744
  filterset_class = filters.ControllerManagedDeviceGroupFilterSet
@@ -4509,42 +4749,88 @@ class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
4509
4749
  serializer_class = serializers.ControllerManagedDeviceGroupSerializer
4510
4750
  table_class = tables.ControllerManagedDeviceGroupTable
4511
4751
  template_name = "dcim/controllermanageddevicegroup_create.html"
4752
+ object_detail_content = object_detail.ObjectDetailContent(
4753
+ panels=(
4754
+ object_detail.ObjectFieldsPanel(
4755
+ section=SectionChoices.LEFT_HALF,
4756
+ weight=100,
4757
+ fields="__all__",
4758
+ value_transforms={
4759
+ "capabilities": [helpers.label_list],
4760
+ },
4761
+ ),
4762
+ object_detail.ObjectsTablePanel(
4763
+ section=SectionChoices.FULL_WIDTH,
4764
+ weight=100,
4765
+ table_class=tables.DeviceTable,
4766
+ table_filter="controller_managed_device_group",
4767
+ add_button_route=None,
4768
+ ),
4769
+ ),
4770
+ extra_tabs=(
4771
+ object_detail.DistinctViewTab(
4772
+ weight=800,
4773
+ tab_id="wireless_networks",
4774
+ label="Wireless Networks",
4775
+ url_name="dcim:controllermanageddevicegroup_wireless_networks",
4776
+ related_object_attribute="wireless_network_assignments",
4777
+ panels=(
4778
+ object_detail.ObjectsTablePanel(
4779
+ section=SectionChoices.FULL_WIDTH,
4780
+ weight=100,
4781
+ table_title="Wireless Networks",
4782
+ table_class=DeviceGroupWirelessNetworkTable,
4783
+ table_filter="controller_managed_device_group",
4784
+ related_field_name="controller_managed_device_groups",
4785
+ tab_id="wireless_networks",
4786
+ add_button_route=None,
4787
+ exclude_columns=["controller_managed_device_group", "controller"],
4788
+ ),
4789
+ ),
4790
+ ),
4791
+ object_detail.DistinctViewTab(
4792
+ weight=900,
4793
+ tab_id="radio_profiles",
4794
+ label="Radio Profiles",
4795
+ url_name="dcim:controllermanageddevicegroup_radio_profiles",
4796
+ related_object_attribute="radio_profiles",
4797
+ panels=(
4798
+ object_detail.ObjectsTablePanel(
4799
+ section=SectionChoices.FULL_WIDTH,
4800
+ weight=100,
4801
+ table_title="Radio Profiles",
4802
+ table_class=RadioProfileTable,
4803
+ table_filter="controller_managed_device_groups",
4804
+ tab_id="radio_profiles",
4805
+ add_button_route=None,
4806
+ ),
4807
+ ),
4808
+ ),
4809
+ ),
4810
+ )
4512
4811
 
4513
- def get_extra_context(self, request, instance):
4514
- context = super().get_extra_context(request, instance)
4515
-
4516
- if self.action == "retrieve" and instance:
4517
- devices = instance.devices.restrict(request.user)
4518
- devices_table = tables.DeviceTable(devices)
4519
-
4520
- paginate = {
4521
- "paginator_class": EnhancedPaginator,
4522
- "per_page": get_paginate_count(request),
4523
- }
4524
- RequestConfig(request, paginate).configure(devices_table)
4525
-
4526
- context["devices_table"] = devices_table
4812
+ @action(
4813
+ detail=True,
4814
+ url_path="wireless-networks",
4815
+ url_name="wireless_networks",
4816
+ custom_view_base_action="view",
4817
+ custom_view_additional_permissions=["wireless.view_controllermanageddevicegroupwirelessnetworkassignment"],
4818
+ )
4819
+ def wireless_networks(self, request, *args, **kwargs):
4820
+ return Response({})
4527
4821
 
4528
- # Wireless Networks
4529
- wireless_networks = instance.wireless_network_assignments.restrict(request.user, "view")
4530
- wireless_networks_table = ControllerManagedDeviceGroupWirelessNetworkAssignmentTable(wireless_networks)
4531
- wireless_networks_table.columns.hide("controller_managed_device_group")
4532
- wireless_networks_table.columns.hide("controller")
4533
- RequestConfig(
4534
- request, paginate={"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
4535
- ).configure(wireless_networks_table)
4536
- context["wireless_networks_table"] = wireless_networks_table
4537
- context["wireless_networks_count"] = wireless_networks.count()
4538
-
4539
- # Radio Profiles
4540
- radio_profiles = instance.radio_profiles.restrict(request.user, "view")
4541
- radio_profiles_table = RadioProfileTable(radio_profiles)
4542
- RequestConfig(
4543
- request, paginate={"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
4544
- ).configure(radio_profiles_table)
4545
- context["radio_profiles_table"] = radio_profiles_table
4546
- context["radio_profiles_count"] = radio_profiles.count()
4822
+ @action(
4823
+ detail=True,
4824
+ url_path="radio-profiles",
4825
+ url_name="radio_profiles",
4826
+ custom_view_base_action="view",
4827
+ custom_view_additional_permissions=["wireless.view_radioprofile"],
4828
+ )
4829
+ def radio_profiles(self, request, *args, **kwargs):
4830
+ return Response({})
4547
4831
 
4832
+ def get_extra_context(self, request, instance):
4833
+ context = super().get_extra_context(request, instance)
4548
4834
  if self.action in ["create", "update"]:
4549
4835
  context["wireless_networks"] = ControllerManagedDeviceGroupWirelessNetworkFormSet(
4550
4836
  instance=instance,