nautobot 2.4.16__py3-none-any.whl → 2.4.18__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 (434) hide show
  1. nautobot/apps/utils.py +2 -0
  2. nautobot/apps/views.py +2 -0
  3. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
  4. nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
  5. nautobot/circuits/tests/integration/test_circuit.py +2 -2
  6. nautobot/circuits/views.py +32 -15
  7. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
  8. nautobot/cloud/views.py +7 -0
  9. nautobot/core/apps/__init__.py +1 -0
  10. nautobot/core/celery/__init__.py +2 -1
  11. nautobot/core/filters.py +2 -2
  12. nautobot/core/settings.py +1 -0
  13. nautobot/core/settings.yaml +9 -0
  14. nautobot/core/tables.py +21 -23
  15. nautobot/core/templates/components/breadcrumbs.html +19 -0
  16. nautobot/core/templates/components/panel/panel.html +1 -1
  17. nautobot/core/templates/generic/object_changelog.html +0 -2
  18. nautobot/core/templates/generic/object_list.html +15 -12
  19. nautobot/core/templates/generic/object_notes.html +0 -2
  20. nautobot/core/templates/generic/object_retrieve.html +16 -9
  21. nautobot/core/templates/inc/paginator.html +3 -3
  22. nautobot/core/templates/inc/table.html +2 -2
  23. nautobot/core/templatetags/helpers.py +104 -6
  24. nautobot/core/templatetags/ui_framework.py +40 -5
  25. nautobot/core/testing/filters.py +37 -21
  26. nautobot/core/testing/mixins.py +1 -1
  27. nautobot/core/testing/views.py +27 -4
  28. nautobot/core/tests/test_tables.py +43 -6
  29. nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
  30. nautobot/core/tests/test_titles.py +2 -2
  31. nautobot/core/tests/test_ui.py +14 -1
  32. nautobot/core/tests/test_views.py +45 -0
  33. nautobot/core/ui/breadcrumbs.py +13 -8
  34. nautobot/core/ui/bulk_buttons.py +53 -53
  35. nautobot/core/ui/object_detail.py +52 -9
  36. nautobot/core/ui/titles.py +9 -5
  37. nautobot/core/utils/data.py +13 -0
  38. nautobot/core/utils/deprecation.py +2 -0
  39. nautobot/core/views/__init__.py +24 -3
  40. nautobot/core/views/generic.py +42 -17
  41. nautobot/core/views/mixins.py +146 -12
  42. nautobot/core/views/utils.py +117 -0
  43. nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
  44. nautobot/dcim/models/device_component_templates.py +4 -2
  45. nautobot/dcim/models/device_components.py +3 -2
  46. nautobot/dcim/models/devices.py +4 -0
  47. nautobot/dcim/tables/__init__.py +2 -0
  48. nautobot/dcim/tables/devices.py +24 -0
  49. nautobot/dcim/tables/power.py +2 -2
  50. nautobot/dcim/templates/dcim/device/base.html +1 -11
  51. nautobot/dcim/templates/dcim/device_component.html +0 -19
  52. nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -16
  53. nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
  54. nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
  55. nautobot/dcim/tests/test_views.py +41 -0
  56. nautobot/dcim/views.py +169 -39
  57. nautobot/extras/filters/mixins.py +1 -1
  58. nautobot/extras/forms/forms.py +15 -0
  59. nautobot/extras/models/customfields.py +45 -9
  60. nautobot/extras/models/groups.py +10 -1
  61. nautobot/extras/models/jobs.py +2 -2
  62. nautobot/extras/plugins/views.py +18 -5
  63. nautobot/extras/tables.py +4 -2
  64. nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
  65. nautobot/extras/templates/extras/configcontext_update.html +49 -49
  66. nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
  67. nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
  68. nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
  69. nautobot/extras/templates/extras/dynamicgroup.html +2 -99
  70. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
  71. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
  72. nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
  73. nautobot/extras/templates/extras/gitrepository.html +2 -82
  74. nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
  75. nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
  76. nautobot/extras/templates/extras/gitrepository_update.html +13 -0
  77. nautobot/extras/templates/extras/inc/job_table.html +1 -1
  78. nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
  79. nautobot/extras/templates/extras/note_retrieve.html +1 -53
  80. nautobot/extras/templates/extras/plugin_detail.html +3 -7
  81. nautobot/extras/templates/extras/plugins_list.html +0 -2
  82. nautobot/extras/templates/extras/tag_retrieve.html +1 -1
  83. nautobot/extras/templates/extras/tag_update.html +14 -14
  84. nautobot/extras/templates/extras/team_retrieve.html +1 -1
  85. nautobot/extras/tests/test_dynamicgroups.py +73 -18
  86. nautobot/extras/tests/test_models.py +216 -0
  87. nautobot/extras/tests/test_views.py +7 -2
  88. nautobot/extras/urls.py +2 -94
  89. nautobot/extras/views.py +425 -430
  90. nautobot/ipam/apps.py +1 -0
  91. nautobot/ipam/jobs/__init__.py +10 -0
  92. nautobot/ipam/jobs/cleanup.py +296 -0
  93. nautobot/ipam/models.py +301 -178
  94. nautobot/ipam/querysets.py +3 -3
  95. nautobot/ipam/signals.py +6 -1
  96. nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
  97. nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
  98. nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
  99. nautobot/ipam/templates/ipam/prefix.html +0 -8
  100. nautobot/ipam/templates/ipam/prefix_list.html +1 -1
  101. nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
  102. nautobot/ipam/tests/test_api.py +5 -0
  103. nautobot/ipam/tests/test_jobs.py +454 -0
  104. nautobot/ipam/tests/test_models.py +677 -122
  105. nautobot/ipam/tests/test_querysets.py +46 -0
  106. nautobot/ipam/tests/test_views.py +40 -164
  107. nautobot/ipam/urls.py +0 -11
  108. nautobot/ipam/utils/migrations.py +1 -1
  109. nautobot/ipam/utils/testing.py +9 -4
  110. nautobot/ipam/views.py +175 -235
  111. nautobot/project-static/docs/404.html +9 -6
  112. nautobot/project-static/docs/apps/index.html +9 -6
  113. nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
  114. nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
  115. nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
  116. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
  117. nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
  118. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
  119. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
  120. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
  121. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
  122. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
  123. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
  124. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
  125. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
  126. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
  127. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
  128. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
  129. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
  130. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
  131. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
  132. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
  133. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
  134. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
  135. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
  136. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
  137. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +81 -6
  138. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +73 -18
  139. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
  140. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
  141. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +402 -21
  142. nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
  143. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
  144. nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
  145. nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
  146. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
  147. nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
  148. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -9
  149. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
  150. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
  151. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
  152. nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
  153. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
  154. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
  155. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
  156. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
  157. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
  158. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
  159. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
  160. nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
  161. nautobot/project-static/docs/development/apps/api/setup.html +9 -6
  162. nautobot/project-static/docs/development/apps/api/testing.html +9 -6
  163. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
  164. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
  165. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
  166. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
  167. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
  168. nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
  169. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
  170. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
  171. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
  172. nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
  173. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
  174. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
  175. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
  176. nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
  177. nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
  178. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
  179. nautobot/project-static/docs/development/apps/index.html +9 -6
  180. nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
  181. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
  182. nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
  183. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
  184. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
  185. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
  186. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
  187. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
  188. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
  189. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
  190. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
  191. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
  192. nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
  193. nautobot/project-static/docs/development/core/application-registry.html +23 -20
  194. nautobot/project-static/docs/development/core/best-practices.html +23 -20
  195. nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
  196. nautobot/project-static/docs/development/core/caching.html +9 -6
  197. nautobot/project-static/docs/development/core/controllers.html +9 -6
  198. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
  199. nautobot/project-static/docs/development/core/generic-views.html +9 -6
  200. nautobot/project-static/docs/development/core/getting-started.html +9 -21
  201. nautobot/project-static/docs/development/core/homepage.html +12 -9
  202. nautobot/project-static/docs/development/core/index.html +9 -6
  203. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
  204. nautobot/project-static/docs/development/core/model-checklist.html +9 -6
  205. nautobot/project-static/docs/development/core/model-features.html +11 -8
  206. nautobot/project-static/docs/development/core/natural-keys.html +21 -18
  207. nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
  208. nautobot/project-static/docs/development/core/release-checklist.html +9 -6
  209. nautobot/project-static/docs/development/core/role-internals.html +9 -6
  210. nautobot/project-static/docs/development/core/settings.html +9 -6
  211. nautobot/project-static/docs/development/core/style-guide.html +32 -29
  212. nautobot/project-static/docs/development/core/templates.html +9 -6
  213. nautobot/project-static/docs/development/core/testing.html +10 -7
  214. nautobot/project-static/docs/development/core/ui-component-framework.html +42 -44
  215. nautobot/project-static/docs/development/core/user-preferences.html +9 -6
  216. nautobot/project-static/docs/development/index.html +9 -6
  217. nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
  218. nautobot/project-static/docs/development/jobs/index.html +9 -6
  219. nautobot/project-static/docs/development/jobs/installation.html +23 -20
  220. nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
  221. nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
  222. nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
  223. nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
  224. nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
  225. nautobot/project-static/docs/development/jobs/testing.html +14 -11
  226. nautobot/project-static/docs/index.html +9 -6
  227. nautobot/project-static/docs/objects.inv +0 -0
  228. nautobot/project-static/docs/overview/application_stack.html +9 -6
  229. nautobot/project-static/docs/overview/design_philosophy.html +9 -6
  230. nautobot/project-static/docs/release-notes/index.html +9 -6
  231. nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
  232. nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
  233. nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
  234. nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
  235. nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
  236. nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
  237. nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
  238. nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
  239. nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
  240. nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
  241. nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
  242. nautobot/project-static/docs/release-notes/version-2.4.html +489 -6
  243. nautobot/project-static/docs/search/search_index.json +1 -1
  244. nautobot/project-static/docs/sitemap.xml +301 -301
  245. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  246. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
  247. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
  248. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
  249. nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
  250. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
  251. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -8
  252. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
  253. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
  254. nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
  255. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
  256. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
  257. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
  258. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
  259. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
  260. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
  261. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
  262. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
  263. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
  264. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
  265. nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
  266. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
  267. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
  268. nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
  269. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
  270. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
  271. nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
  272. nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
  273. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
  274. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
  275. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
  276. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
  277. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
  278. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
  279. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
  280. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
  281. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
  282. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
  283. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
  284. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
  285. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
  286. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
  287. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
  288. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
  289. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
  290. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
  291. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
  292. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
  293. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
  294. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
  295. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
  337. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
  338. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
  339. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
  340. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
  341. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
  342. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
  343. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
  344. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
  345. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
  346. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
  347. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
  348. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
  349. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
  350. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
  351. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
  352. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
  353. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
  354. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
  355. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
  356. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
  357. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
  358. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
  359. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
  360. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
  361. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
  362. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
  363. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
  364. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
  365. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
  366. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
  367. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
  368. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
  369. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
  370. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
  371. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
  372. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
  373. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
  374. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
  375. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
  376. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
  377. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
  378. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
  379. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
  380. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
  381. nautobot/project-static/docs/user-guide/index.html +9 -6
  382. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
  383. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
  384. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
  385. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
  386. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
  387. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
  388. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
  389. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
  390. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
  391. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
  392. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
  393. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
  394. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
  395. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
  396. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
  397. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
  398. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
  399. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
  400. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
  401. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
  402. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
  403. nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
  404. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
  405. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
  406. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
  407. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
  408. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
  409. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
  410. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
  411. nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
  412. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
  413. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
  414. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
  415. nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
  416. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
  417. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
  418. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
  419. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
  420. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
  421. nautobot/project-static/fonts/UFL.txt +96 -96
  422. nautobot/project-static/img/nautobot_icon.svg +32 -34
  423. nautobot/project-static/js/forms.js +35 -2
  424. nautobot/project-static/js/table_sorting_indicator.js +0 -2
  425. nautobot/virtualization/filters.py +7 -0
  426. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/METADATA +8 -8
  427. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/RECORD +431 -421
  428. nautobot/core/templates/inc/breadcrumbs.html +0 -14
  429. nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
  430. nautobot/project-static/docs/requirements.txt +0 -14
  431. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/LICENSE.txt +0 -0
  432. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/NOTICE +0 -0
  433. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/WHEEL +0 -0
  434. {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/entry_points.txt +0 -0
@@ -1,21 +1,2 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
-
4
- {% block breadcrumbs %}
5
- {% if object.device %}
6
- <li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
7
- <li>{{ object.device|hyperlinked_object }}</li>
8
- {% if device_breadcrumb_url %}
9
- <li><a href="{% url device_breadcrumb_url pk=object.device.pk %}">{{ object|meta:"verbose_name_plural"|bettertitle }}</a></li>
10
- {% endif %}
11
- {% else %}
12
- <li><a href="{% url 'dcim:module_list' %}">Modules</a></li>
13
- <li>{{ object.module|hyperlinked_object }}</li>
14
- {% if module_breadcrumb_url %}
15
- <li><a href="{% url module_breadcrumb_url pk=object.module.pk %}">{{ object|meta:"verbose_name_plural"|bettertitle }}</a></li>
16
- {% endif %}
17
- {% endif %}
18
- <li>{{ object|hyperlinked_object }}</li>
19
- {% endblock breadcrumbs %}
20
-
21
- {% block title %}{% if object.device %}{{ object.device }}{% else %}{{ object.module.display }}{% endif %} / {{ object }}{% endblock title %}
@@ -1,21 +1,5 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
- {% block breadcrumbs %}
4
- {% if object.parent_device %}
5
- <li><a href="{% url 'dcim:device_list' %}">Devices</a></li>
6
- <li>{{ object.parent_device|hyperlinked_object }}</li>
7
- {% if device_breadcrumb_url %}
8
- <li><a href="{% url device_breadcrumb_url pk=object.parent_device.pk %}">{{ object|meta:"verbose_name_plural"|bettertitle }}</a></li>
9
- {% endif %}
10
- {% else %}
11
- <li><a href="{% url 'dcim:module_list' %}">Modules</a></li>
12
- <li>{{ object.parent_module|hyperlinked_object }}</li>
13
- {% if module_breadcrumb_url %}
14
- <li><a href="{% url module_breadcrumb_url pk=object.parent_module.pk %}">{{ object|meta:"verbose_name_plural"|bettertitle }}</a></li>
15
- {% endif %}
16
- {% endif %}
17
- <li><a href="{{ object.get_absolute_url }}">{{ object.name }}</a></li>
18
- {% endblock breadcrumbs %}
19
3
  {% block content_left_page %}
20
4
  <div class="panel panel-default">
21
5
  <div class="panel-heading">
@@ -10,12 +10,12 @@
10
10
  <span class="mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
11
11
  </button>
12
12
  <div class="btn-group" role="group">
13
- <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-default{% if rack_face == 'front' %} active{% endif %}">Front</a>
14
- <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-default{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
13
+ <a href="{% url 'dcim:rack_elevation_list' %}{% legacy_querystring request face='front' %}" class="btn btn-default{% if rack_face == 'front' %} active{% endif %}">Front</a>
14
+ <a href="{% url 'dcim:rack_elevation_list' %}{% legacy_querystring request face='rear' %}" class="btn btn-default{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
15
15
  </div>
16
16
  <div class="btn-group" role="group">
17
- <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse=None %}" class="btn btn-default{% if not reverse %} active{% endif %}">Normal</a>
18
- <a href="{% url 'dcim:rack_elevation_list' %}{% querystring request reverse='true' %}" class="btn btn-default{% if reverse %} active{% endif %}">Reversed</a>
17
+ <a href="{% url 'dcim:rack_elevation_list' %}{% legacy_querystring request reverse=None %}" class="btn btn-default{% if not reverse %} active{% endif %}">Normal</a>
18
+ <a href="{% url 'dcim:rack_elevation_list' %}{% legacy_querystring request reverse='true' %}" class="btn btn-default{% if reverse %} active{% endif %}">Reversed</a>
19
19
  </div>
20
20
  {% endblock %}
21
21
 
@@ -1,51 +1,2 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
- {% load helpers %}
3
-
4
- {% block content_left_page %}
5
- <div class="panel panel-default">
6
- <div class="panel-heading">
7
- <strong>Virtual Chassis</strong>
8
- </div>
9
- <table class="table table-hover panel-body attr-table">
10
- <tr>
11
- <td>Domain</td>
12
- <td>{{ object.domain|placeholder }}</td>
13
- </tr>
14
- <tr>
15
- <td>Master</td>
16
- <td>{{ object.master|hyperlinked_object }}</td>
17
- </tr>
18
- </table>
19
- </div>
20
- {% endblock content_left_page %}
21
-
22
- {% block content_right_page %}
23
- <div class="panel panel-default">
24
- <div class="panel-heading">
25
- <strong>Members</strong>
26
- </div>
27
- <table class="table table-hover panel-body attr-table">
28
- <tr>
29
- <th>Device</th>
30
- <th>Position</th>
31
- <th>Master</th>
32
- <th>Priority</th>
33
- </tr>
34
- {% for vc_member in members %}
35
- <tr{% if vc_member == device %} class="info"{% endif %}>
36
- <td>{{ vc_member|hyperlinked_object }}</td>
37
- <td><span class="badge badge-default">{{ vc_member.vc_position }}</span></td>
38
- <td>{% if object.master == vc_member %}{{ True | render_boolean }}{% endif %}</td>
39
- <td>{{ vc_member.vc_priority|placeholder }}</td>
40
- </tr>
41
- {% endfor %}
42
- </table>
43
- {% if perms.dcim.change_virtualchassis %}
44
- <div class="panel-footer text-right noprint">
45
- <a href="{% url 'dcim:virtualchassis_add_member' pk=object.pk %}?location={{ object.master.location.pk }}&rack={{ object.master.rack.pk }}" class="btn btn-primary btn-xs">
46
- <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add Member
47
- </a>
48
- </div>
49
- {% endif %}
50
- </div>
51
- {% endblock content_right_page %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -4202,6 +4202,47 @@ class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
4202
4202
  # Sanity check:
4203
4203
  self.assertBodyContains(response, "<th>Name</th>", html=True)
4204
4204
 
4205
+ def test_set_master_after_adding_member(self):
4206
+ """Ensure master can be set for a member that was added via the Add Member flow."""
4207
+ self.add_permissions(
4208
+ "dcim.view_device",
4209
+ "dcim.view_virtualchassis",
4210
+ "dcim.change_virtualchassis",
4211
+ "dcim.change_device",
4212
+ )
4213
+
4214
+ # Create VC
4215
+ vc = VirtualChassis.objects.create(name="VC-test", domain="domain-test")
4216
+
4217
+ # Simulate adding a member via the separate "add-member" flow by creating the device with virtual_chassis
4218
+ member = Device.objects.create(
4219
+ device_type=self.devices[0].device_type,
4220
+ role=self.devices[0].role,
4221
+ status=self.devices[0].status,
4222
+ name="separately-added-device",
4223
+ location=self.devices[0].location,
4224
+ virtual_chassis=vc,
4225
+ vc_position=1,
4226
+ )
4227
+
4228
+ # Now edit the VC and set the master to the existing member
4229
+ payload_data = {
4230
+ "name": vc.name,
4231
+ "domain": vc.domain,
4232
+ "master": str(member.pk),
4233
+ # no members formset rows are required because the member already exists
4234
+ "form-TOTAL_FORMS": "0",
4235
+ "form-INITIAL_FORMS": "0",
4236
+ "form-MIN_NUM_FORMS": "0",
4237
+ "form-MAX_NUM_FORMS": "1000",
4238
+ }
4239
+
4240
+ url = reverse("dcim:virtualchassis_edit", kwargs={"pk": vc.pk})
4241
+ self.client.post(url, data=payload_data, follow=True)
4242
+
4243
+ vc.refresh_from_db()
4244
+ self.assertEqual(vc.master, member)
4245
+
4205
4246
 
4206
4247
  class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
4207
4248
  model = PowerPanel
nautobot/dcim/views.py CHANGED
@@ -21,7 +21,7 @@ from django.urls import reverse
21
21
  from django.utils.encoding import iri_to_uri
22
22
  from django.utils.functional import cached_property
23
23
  from django.utils.html import format_html
24
- from django.utils.http import url_has_allowed_host_and_scheme
24
+ from django.utils.http import url_has_allowed_host_and_scheme, urlencode
25
25
  from django.views.generic import View
26
26
  from django_tables2 import RequestConfig
27
27
  from rest_framework.decorators import action
@@ -42,6 +42,7 @@ from nautobot.core.ui.breadcrumbs import (
42
42
  Breadcrumbs,
43
43
  InstanceBreadcrumbItem,
44
44
  ModelBreadcrumbItem,
45
+ ViewNameBreadcrumbItem,
45
46
  )
46
47
  from nautobot.core.ui.bulk_buttons import (
47
48
  BulkDeleteButton,
@@ -49,6 +50,7 @@ from nautobot.core.ui.bulk_buttons import (
49
50
  BulkRenameButton,
50
51
  )
51
52
  from nautobot.core.ui.choices import SectionChoices
53
+ from nautobot.core.ui.titles import Titles
52
54
  from nautobot.core.utils.lookup import get_form_for_model
53
55
  from nautobot.core.utils.permissions import get_permission_for_model
54
56
  from nautobot.core.utils.requests import normalize_querydict
@@ -515,6 +517,7 @@ class RackGroupUIViewSet(NautobotUIViewSet):
515
517
  if self.action == "retrieve" and instance:
516
518
  racks = (
517
519
  Rack.objects.restrict(request.user, "view")
520
+ # Note this filter - we want the table to include racks assigned to child rack groups as well
518
521
  .filter(rack_group__in=instance.descendants(include_self=True))
519
522
  .select_related("role", "location", "tenant")
520
523
  )
@@ -590,6 +593,10 @@ class RackElevationListView(generic.ObjectListView):
590
593
  filterset_form = forms.RackFilterForm
591
594
  action_buttons = []
592
595
  template_name = "dcim/rack_elevation_list.html"
596
+ view_titles = Titles(titles={"list": "Rack Elevation"})
597
+ breadcrumbs = Breadcrumbs(
598
+ items={"list": [ViewNameBreadcrumbItem(view_name="dcim:rack_elevation_list", label="Rack Elevation")]}
599
+ )
593
600
 
594
601
  def extra_context(self):
595
602
  racks = self.queryset
@@ -644,11 +651,15 @@ class RackReservationUIViewSet(NautobotUIViewSet):
644
651
 
645
652
  object_detail_content = object_detail.ObjectDetailContent(
646
653
  panels=(
647
- object_detail.KeyValueTablePanel(
654
+ object_detail.ObjectFieldsPanel(
648
655
  section=SectionChoices.LEFT_HALF,
649
656
  weight=100,
650
657
  label="Rack",
651
- context_data_key="rack_data",
658
+ fields=["rack__location", "rack__rack_group", "rack"],
659
+ key_transforms={
660
+ "rack__location": "Location",
661
+ "rack__rack_group": "Rack Group",
662
+ },
652
663
  ),
653
664
  object_detail.ObjectFieldsPanel(
654
665
  section=SectionChoices.LEFT_HALF,
@@ -664,23 +675,6 @@ class RackReservationUIViewSet(NautobotUIViewSet):
664
675
  ),
665
676
  )
666
677
 
667
- def get_extra_context(self, request, instance):
668
- context = super().get_extra_context(request, instance)
669
- if self.action == "retrieve":
670
- context["rack_data"] = self.get_rack_context(instance)
671
- return context
672
-
673
- def get_rack_context(self, instance):
674
- rack = getattr(instance, "rack", None)
675
- if not rack:
676
- return {}
677
-
678
- return {
679
- "location": rack.location,
680
- "rack_group": rack.rack_group,
681
- "rack": rack,
682
- }
683
-
684
678
  def get_object(self):
685
679
  obj = super().get_object()
686
680
 
@@ -899,6 +893,7 @@ def make_bulk_tab(weight, tab_name, label, url_name, related_attr, table_class,
899
893
  enable_bulk_actions=True,
900
894
  form_id=form_id,
901
895
  footer_buttons=bulk_footer_buttons(form_id=form_id, model=model),
896
+ include_paginator=True,
902
897
  ),
903
898
  ),
904
899
  )
@@ -1889,13 +1884,14 @@ class PlatformUIViewSet(NautobotUIViewSet):
1889
1884
  #
1890
1885
 
1891
1886
 
1892
- class DeviceBreadcrumbsMixin:
1887
+ class DevicePageMixin:
1893
1888
  breadcrumbs = Breadcrumbs(
1894
1889
  items={
1895
1890
  "detail": [
1896
1891
  ModelBreadcrumbItem(model=Device),
1897
1892
  ModelBreadcrumbItem(
1898
1893
  model=Device,
1894
+ label=lambda c: c["object"].location,
1899
1895
  reverse_query_params=lambda c: {"location": c["object"].location.pk},
1900
1896
  ),
1901
1897
  InstanceBreadcrumbItem(
@@ -1910,6 +1906,49 @@ class DeviceBreadcrumbsMixin:
1910
1906
  )
1911
1907
 
1912
1908
 
1909
+ class DeviceComponentPageMixin:
1910
+ """
1911
+ This class hold the breadcrumbs paths for Device Components Pages like console ports.
1912
+ Depending on whether the component is associated with a device or a module, the appropriate breadcrumb path will be rendered.
1913
+
1914
+ For example:
1915
+ - Console Port assigned to the module: Modules / <Module name and link to details> / Console Ports (dcim/modules/<id>/console-ports/) / <Console Port name>
1916
+ - Console Port assigned to the device: Devices / <Device name and link to details> / Console Ports (dcim/devices/<id>/console-ports/) / <Console Port name>
1917
+ """
1918
+
1919
+ breadcrumbs = Breadcrumbs(
1920
+ items={
1921
+ "detail": [
1922
+ ModelBreadcrumbItem(model=Device, should_render=lambda c: c["object"].device is not None),
1923
+ InstanceBreadcrumbItem(
1924
+ instance=lambda c: c["object"].device, should_render=lambda c: c["object"].device is not None
1925
+ ),
1926
+ ViewNameBreadcrumbItem(
1927
+ view_name_key="device_breadcrumb_url",
1928
+ should_render=lambda c: c["object"].device is not None and c.get("device_breadcrumb_url"),
1929
+ reverse_kwargs=lambda c: {"pk": c["object"].device.pk},
1930
+ label=lambda c: c["object"]._meta.verbose_name_plural,
1931
+ ),
1932
+ ModelBreadcrumbItem(model=Module, should_render=lambda c: c["object"].device is None),
1933
+ InstanceBreadcrumbItem(
1934
+ instance=lambda c: c["object"].module, should_render=lambda c: c["object"].device is None
1935
+ ),
1936
+ ViewNameBreadcrumbItem(
1937
+ view_name_key="module_breadcrumb_url",
1938
+ should_render=lambda c: c["object"].device is None and c.get("module_breadcrumb_url"),
1939
+ reverse_kwargs=lambda c: {"pk": c["object"].module.pk},
1940
+ label=lambda c: c["object"]._meta.verbose_name_plural,
1941
+ ),
1942
+ ]
1943
+ }
1944
+ )
1945
+ view_titles = Titles(
1946
+ titles={
1947
+ "detail": "{% if object.device %}{{ object.device }}{% else %}{{ object.module.display }}{% endif %} / {{ object }}"
1948
+ }
1949
+ )
1950
+
1951
+
1913
1952
  class DeviceListView(generic.ObjectListView):
1914
1953
  queryset = Device.objects.select_related(
1915
1954
  "device_type__manufacturer", # Needed for __str__() on device_type
@@ -1920,7 +1959,7 @@ class DeviceListView(generic.ObjectListView):
1920
1959
  template_name = "dcim/device_list.html"
1921
1960
 
1922
1961
 
1923
- class DeviceView(generic.ObjectView):
1962
+ class DeviceView(DevicePageMixin, generic.ObjectView):
1924
1963
  queryset = Device.objects.select_related(
1925
1964
  "cluster__cluster_group",
1926
1965
  "controller_managed_device_group__controller",
@@ -1965,6 +2004,7 @@ class DeviceView(generic.ObjectView):
1965
2004
  exclude_columns=["content_type"],
1966
2005
  add_button_route=None,
1967
2006
  related_field_name="member_id",
2007
+ include_paginator=True,
1968
2008
  ),
1969
2009
  ),
1970
2010
  )
@@ -2175,7 +2215,7 @@ class DeviceView(generic.ObjectView):
2175
2215
  }
2176
2216
 
2177
2217
 
2178
- class DeviceComponentTabView(generic.ObjectView):
2218
+ class DeviceComponentTabView(DevicePageMixin, generic.ObjectView):
2179
2219
  queryset = Device.objects.all()
2180
2220
 
2181
2221
  def get_extra_context(self, request, instance):
@@ -2183,6 +2223,7 @@ class DeviceComponentTabView(generic.ObjectView):
2183
2223
  module_count = instance.module_bays.filter(installed_module__isnull=False).count()
2184
2224
 
2185
2225
  return {
2226
+ **super().get_extra_context(request, instance),
2186
2227
  "modulebay_count": modulebay_count,
2187
2228
  "module_count": f"{module_count}/{modulebay_count}",
2188
2229
  }
@@ -2442,7 +2483,7 @@ class DeviceConfigView(generic.ObjectView):
2442
2483
  }
2443
2484
 
2444
2485
 
2445
- class DeviceConfigContextView(ObjectConfigContextView):
2486
+ class DeviceConfigContextView(DevicePageMixin, ObjectConfigContextView):
2446
2487
  base_template = "dcim/device/base.html"
2447
2488
 
2448
2489
  @cached_property
@@ -2453,7 +2494,7 @@ class DeviceConfigContextView(ObjectConfigContextView):
2453
2494
  return Device.objects.annotate_config_context_data()
2454
2495
 
2455
2496
 
2456
- class DeviceChangeLogView(ObjectChangeLogView):
2497
+ class DeviceChangeLogView(DevicePageMixin, ObjectChangeLogView):
2457
2498
  base_template = "dcim/device/base.html"
2458
2499
 
2459
2500
 
@@ -3005,7 +3046,7 @@ class ConsolePortListView(generic.ObjectListView):
3005
3046
  action_buttons = ("import", "export")
3006
3047
 
3007
3048
 
3008
- class ConsolePortView(generic.ObjectView):
3049
+ class ConsolePortView(DeviceComponentPageMixin, generic.ObjectView):
3009
3050
  queryset = ConsolePort.objects.all()
3010
3051
 
3011
3052
  def get_extra_context(self, request, instance):
@@ -3071,7 +3112,7 @@ class ConsoleServerPortListView(generic.ObjectListView):
3071
3112
  action_buttons = ("import", "export")
3072
3113
 
3073
3114
 
3074
- class ConsoleServerPortView(generic.ObjectView):
3115
+ class ConsoleServerPortView(DeviceComponentPageMixin, generic.ObjectView):
3075
3116
  queryset = ConsoleServerPort.objects.all()
3076
3117
 
3077
3118
  def get_extra_context(self, request, instance):
@@ -3137,7 +3178,7 @@ class PowerPortListView(generic.ObjectListView):
3137
3178
  action_buttons = ("import", "export")
3138
3179
 
3139
3180
 
3140
- class PowerPortView(generic.ObjectView):
3181
+ class PowerPortView(DeviceComponentPageMixin, generic.ObjectView):
3141
3182
  queryset = PowerPort.objects.all()
3142
3183
 
3143
3184
  def get_extra_context(self, request, instance):
@@ -3203,7 +3244,7 @@ class PowerOutletListView(generic.ObjectListView):
3203
3244
  action_buttons = ("import", "export")
3204
3245
 
3205
3246
 
3206
- class PowerOutletView(generic.ObjectView):
3247
+ class PowerOutletView(DeviceComponentPageMixin, generic.ObjectView):
3207
3248
  queryset = PowerOutlet.objects.all()
3208
3249
 
3209
3250
  def get_extra_context(self, request, instance):
@@ -3269,7 +3310,10 @@ class InterfaceListView(generic.ObjectListView):
3269
3310
  action_buttons = ("import", "export")
3270
3311
 
3271
3312
 
3272
- class InterfaceView(generic.ObjectView):
3313
+ class InterfaceView(
3314
+ DeviceComponentPageMixin,
3315
+ generic.ObjectView,
3316
+ ):
3273
3317
  queryset = Interface.objects.all()
3274
3318
 
3275
3319
  def get_extra_context(self, request, instance):
@@ -3399,7 +3443,7 @@ class FrontPortListView(generic.ObjectListView):
3399
3443
  action_buttons = ("import", "export")
3400
3444
 
3401
3445
 
3402
- class FrontPortView(generic.ObjectView):
3446
+ class FrontPortView(DeviceComponentPageMixin, generic.ObjectView):
3403
3447
  queryset = FrontPort.objects.all()
3404
3448
 
3405
3449
  def get_extra_context(self, request, instance):
@@ -3465,7 +3509,7 @@ class RearPortListView(generic.ObjectListView):
3465
3509
  action_buttons = ("import", "export")
3466
3510
 
3467
3511
 
3468
- class RearPortView(generic.ObjectView):
3512
+ class RearPortView(DeviceComponentPageMixin, generic.ObjectView):
3469
3513
  queryset = RearPort.objects.all()
3470
3514
 
3471
3515
  def get_extra_context(self, request, instance):
@@ -3531,7 +3575,7 @@ class DeviceBayListView(generic.ObjectListView):
3531
3575
  action_buttons = ("import", "export")
3532
3576
 
3533
3577
 
3534
- class DeviceBayView(generic.ObjectView):
3578
+ class DeviceBayView(DeviceComponentPageMixin, generic.ObjectView):
3535
3579
  queryset = DeviceBay.objects.all()
3536
3580
 
3537
3581
  def get_extra_context(self, request, instance):
@@ -3693,6 +3737,35 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
3693
3737
  ),
3694
3738
  )
3695
3739
  )
3740
+ breadcrumbs = Breadcrumbs(
3741
+ items={
3742
+ "detail": [
3743
+ # Breadcrumb path if ModuleBay is linked with device
3744
+ ModelBreadcrumbItem(model=Device, should_render=lambda c: c["object"].parent_device),
3745
+ InstanceBreadcrumbItem(
3746
+ instance=lambda c: c["object"].parent_device, should_render=lambda c: c["object"].parent_device
3747
+ ),
3748
+ ViewNameBreadcrumbItem(
3749
+ view_name_key="device_breadcrumb_url",
3750
+ should_render=lambda c: c["object"].parent_device and c.get("device_breadcrumb_url"),
3751
+ reverse_kwargs=lambda c: {"pk": c["object"].parent_device.pk},
3752
+ label=lambda c: c["object"]._meta.verbose_name_plural,
3753
+ ),
3754
+ # Breadcrumb path if ModuleBay is linked with module
3755
+ ModelBreadcrumbItem(model=Module, should_render=lambda c: c["object"].parent_device is None),
3756
+ InstanceBreadcrumbItem(
3757
+ instance=lambda c: c["object"].parent_module,
3758
+ should_render=lambda c: c["object"].parent_device is None,
3759
+ ),
3760
+ ViewNameBreadcrumbItem(
3761
+ view_name_key="module_breadcrumb_url",
3762
+ should_render=lambda c: c["object"].parent_device is None and c.get("module_breadcrumb_url"),
3763
+ reverse_kwargs=lambda c: {"pk": c["object"].parent_module.pk},
3764
+ label=lambda c: c["object"]._meta.verbose_name_plural,
3765
+ ),
3766
+ ]
3767
+ }
3768
+ )
3696
3769
 
3697
3770
  def get_extra_context(self, request, instance):
3698
3771
  context = super().get_extra_context(request, instance)
@@ -3746,7 +3819,7 @@ class InventoryItemListView(generic.ObjectListView):
3746
3819
  action_buttons = ("import", "export")
3747
3820
 
3748
3821
 
3749
- class InventoryItemView(generic.ObjectView):
3822
+ class InventoryItemView(DeviceComponentPageMixin, generic.ObjectView):
3750
3823
  queryset = InventoryItem.objects.all().select_related("device", "manufacturer", "software_version")
3751
3824
 
3752
3825
  def get_extra_context(self, request, instance):
@@ -4106,6 +4179,10 @@ class ConsoleConnectionsListView(ConnectionsListView):
4106
4179
  table = tables.ConsoleConnectionTable
4107
4180
  template_name = "dcim/console_port_connection_list.html"
4108
4181
  action_buttons = ("export",)
4182
+ view_titles = Titles(titles={"list": "Console Connections"})
4183
+ breadcrumbs = Breadcrumbs(
4184
+ items={"list": [ViewNameBreadcrumbItem(view_name="dcim:console_connections_list", label="Console Connections")]}
4185
+ )
4109
4186
 
4110
4187
  def extra_context(self):
4111
4188
  return {
@@ -4122,6 +4199,10 @@ class PowerConnectionsListView(ConnectionsListView):
4122
4199
  table = tables.PowerConnectionTable
4123
4200
  template_name = "dcim/power_port_connection_list.html"
4124
4201
  action_buttons = ("export",)
4202
+ view_titles = Titles(titles={"list": "Power Connections"})
4203
+ breadcrumbs = Breadcrumbs(
4204
+ items={"list": [ViewNameBreadcrumbItem(view_name="dcim:power_connections_list", label="Power Connections")]}
4205
+ )
4125
4206
 
4126
4207
  def extra_context(self):
4127
4208
  return {
@@ -4138,6 +4219,12 @@ class InterfaceConnectionsListView(ConnectionsListView):
4138
4219
  table = tables.InterfaceConnectionTable
4139
4220
  template_name = "dcim/interface_connection_list.html"
4140
4221
  action_buttons = ("export",)
4222
+ view_titles = Titles(titles={"list": "Interface Connections"})
4223
+ breadcrumbs = Breadcrumbs(
4224
+ items={
4225
+ "list": [ViewNameBreadcrumbItem(view_name="dcim:interface_connections_list", label="Interface Connections")]
4226
+ }
4227
+ )
4141
4228
 
4142
4229
  def __init__(self, *args, **kwargs):
4143
4230
  super().__init__(*args, **kwargs)
@@ -4180,10 +4267,50 @@ class VirtualChassisUIViewSet(NautobotUIViewSet):
4180
4267
  bulk_update_form_class = forms.VirtualChassisBulkEditForm
4181
4268
  filterset_class = filters.VirtualChassisFilterSet
4182
4269
  filterset_form_class = forms.VirtualChassisFilterForm
4183
- form_class = forms.VirtualChassisCreateForm
4184
4270
  serializer_class = serializers.VirtualChassisSerializer
4185
4271
  table_class = tables.VirtualChassisTable
4186
4272
  queryset = VirtualChassis.objects.all()
4273
+ create_form_class = forms.VirtualChassisCreateForm
4274
+ update_form_class = forms.VirtualChassisForm
4275
+
4276
+ class MembersObjectsTablePanel(object_detail.ObjectsTablePanel):
4277
+ def _get_table_add_url(self, context):
4278
+ obj = get_obj_from_context(context)
4279
+ request = context["request"]
4280
+ return_url = context.get("return_url", obj.get_absolute_url())
4281
+
4282
+ if not request.user.has_perm("dcim.change_virtualchassis"):
4283
+ return None
4284
+
4285
+ params = []
4286
+ master = obj.master
4287
+
4288
+ if master is not None:
4289
+ if master.location is not None:
4290
+ params.append(("location", master.location.pk))
4291
+
4292
+ if master.rack is not None:
4293
+ params.append(("rack", master.rack.pk))
4294
+
4295
+ params.append(("return_url", return_url))
4296
+ return reverse("dcim:virtualchassis_add_member", kwargs={"pk": obj.pk}) + "?" + urlencode(params)
4297
+
4298
+ object_detail_content = object_detail.ObjectDetailContent(
4299
+ panels=[
4300
+ object_detail.ObjectFieldsPanel(
4301
+ section=SectionChoices.LEFT_HALF,
4302
+ weight=100,
4303
+ fields="__all__",
4304
+ ),
4305
+ MembersObjectsTablePanel(
4306
+ section=SectionChoices.RIGHT_HALF,
4307
+ weight=100,
4308
+ table_class=tables.VirtualChassisMembersTable,
4309
+ table_filter="virtual_chassis",
4310
+ table_title="Members",
4311
+ ),
4312
+ ]
4313
+ )
4187
4314
 
4188
4315
  def get_extra_context(self, request, instance):
4189
4316
  context = super().get_extra_context(request, instance)
@@ -4213,10 +4340,6 @@ class VirtualChassisUIViewSet(NautobotUIViewSet):
4213
4340
  }
4214
4341
  )
4215
4342
 
4216
- elif self.action == "retrieve":
4217
- members = Device.objects.restrict(request.user).filter(virtual_chassis=instance)
4218
- context.update({"members": members})
4219
-
4220
4343
  return context
4221
4344
 
4222
4345
  def form_save(self, form, **kwargs):
@@ -4794,6 +4917,7 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4794
4917
  table_filter="software_image_files",
4795
4918
  tab_id="device_types",
4796
4919
  add_button_route=None,
4920
+ include_paginator=True,
4797
4921
  ),
4798
4922
  ),
4799
4923
  ),
@@ -4812,6 +4936,7 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4812
4936
  tab_id="devices",
4813
4937
  table_title="Devices overridden to use this file",
4814
4938
  add_button_route=None,
4939
+ include_paginator=True,
4815
4940
  ),
4816
4941
  ),
4817
4942
  ),
@@ -4830,6 +4955,7 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4830
4955
  tab_id="inventory_items",
4831
4956
  table_title="Inventory items overridden to use this file",
4832
4957
  add_button_route=None,
4958
+ include_paginator=True,
4833
4959
  ),
4834
4960
  ),
4835
4961
  ),
@@ -4848,6 +4974,7 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
4848
4974
  tab_id="virtual_machines",
4849
4975
  table_title="Virtual machines overridden to use this file",
4850
4976
  add_button_route=None,
4977
+ include_paginator=True,
4851
4978
  ),
4852
4979
  ),
4853
4980
  ),
@@ -4993,6 +5120,7 @@ class ControllerUIViewSet(NautobotUIViewSet):
4993
5120
  add_button_route=None,
4994
5121
  select_related_fields=["wireless_network"],
4995
5122
  exclude_columns=["controller"],
5123
+ include_paginator=True,
4996
5124
  ),
4997
5125
  ),
4998
5126
  ),
@@ -5056,6 +5184,7 @@ class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
5056
5184
  tab_id="wireless_networks",
5057
5185
  add_button_route=None,
5058
5186
  exclude_columns=["controller_managed_device_group", "controller"],
5187
+ include_paginator=True,
5059
5188
  ),
5060
5189
  ),
5061
5190
  ),
@@ -5074,6 +5203,7 @@ class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
5074
5203
  table_filter="controller_managed_device_groups",
5075
5204
  tab_id="radio_profiles",
5076
5205
  add_button_route=None,
5206
+ include_paginator=True,
5077
5207
  ),
5078
5208
  ),
5079
5209
  ),
@@ -209,7 +209,7 @@ class RelationshipFilter(django_filters.ModelMultipleChoiceFilter):
209
209
  return query
210
210
 
211
211
  def filter(self, qs, value):
212
- if not value or any(v in EMPTY_VALUES for v in value):
212
+ if not value or any(v in EMPTY_VALUES for v in value) or (hasattr(value, "exists") and not value.exists()):
213
213
  return qs
214
214
 
215
215
  query = self.generate_query(value)
@@ -144,6 +144,7 @@ __all__ = (
144
144
  "CustomLinkFilterForm",
145
145
  "CustomLinkForm",
146
146
  "DynamicGroupBulkAssignForm",
147
+ "DynamicGroupBulkEditForm",
147
148
  "DynamicGroupFilterForm",
148
149
  "DynamicGroupForm",
149
150
  "DynamicGroupMembershipFormSet",
@@ -704,6 +705,20 @@ class CustomLinkFilterForm(BootstrapMixin, forms.Form):
704
705
  #
705
706
  # Dynamic Groups
706
707
  #
708
+ class DynamicGroupBulkEditForm(NautobotBulkEditForm):
709
+ pk = forms.ModelMultipleChoiceField(queryset=DynamicGroup.objects.all(), widget=forms.MultipleHiddenInput())
710
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
711
+ tenant = DynamicModelChoiceField(
712
+ queryset=Tenant.objects.all(),
713
+ required=False,
714
+ )
715
+
716
+ class Meta:
717
+ model = DynamicGroup
718
+ fields = [
719
+ "description",
720
+ "tenant",
721
+ ]
707
722
 
708
723
 
709
724
  class DynamicGroupForm(TenancyForm, NautobotModelForm):