nautobot 2.3.0b1__py3-none-any.whl → 2.3.2__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 (399) hide show
  1. nautobot/cloud/factory.py +2 -0
  2. nautobot/cloud/filters.py +3 -0
  3. nautobot/cloud/forms.py +8 -2
  4. nautobot/cloud/migrations/0001_initial.py +1 -1
  5. nautobot/cloud/models.py +1 -2
  6. nautobot/cloud/tables.py +1 -17
  7. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
  8. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +11 -0
  9. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +4 -0
  10. nautobot/cloud/tests/test_filters.py +12 -0
  11. nautobot/cloud/tests/test_views.py +17 -0
  12. nautobot/cloud/views.py +1 -1
  13. nautobot/core/celery/__init__.py +5 -2
  14. nautobot/core/celery/schedulers.py +18 -0
  15. nautobot/core/filters.py +15 -1
  16. nautobot/core/forms/forms.py +10 -2
  17. nautobot/core/graphql/generators.py +2 -2
  18. nautobot/core/graphql/schema.py +6 -14
  19. nautobot/core/jobs/__init__.py +4 -1
  20. nautobot/core/management/commands/generate_test_data.py +2 -2
  21. nautobot/core/models/__init__.py +2 -2
  22. nautobot/core/settings.py +13 -2
  23. nautobot/core/settings.yaml +19 -5
  24. nautobot/core/tables.py +4 -1
  25. nautobot/core/templates/generic/object_retrieve.html +6 -6
  26. nautobot/core/templates/home.html +4 -3
  27. nautobot/core/templates/inc/computed_fields/panel_data.html +36 -24
  28. nautobot/core/templates/inc/object_details_advanced_panel.html +1 -1
  29. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  30. nautobot/core/templatetags/buttons.py +1 -1
  31. nautobot/core/testing/filters.py +12 -1
  32. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  33. nautobot/core/tests/test_jobs.py +74 -1
  34. nautobot/core/views/__init__.py +1 -1
  35. nautobot/core/views/generic.py +1 -1
  36. nautobot/core/views/mixins.py +1 -1
  37. nautobot/core/views/utils.py +11 -9
  38. nautobot/dcim/factory.py +7 -4
  39. nautobot/dcim/filters/__init__.py +4 -0
  40. nautobot/dcim/forms.py +24 -0
  41. nautobot/dcim/migrations/0061_module_models.py +1 -0
  42. nautobot/dcim/models/device_components.py +7 -0
  43. nautobot/dcim/models/devices.py +18 -19
  44. nautobot/dcim/models/racks.py +0 -1
  45. nautobot/dcim/tables/devices.py +24 -10
  46. nautobot/dcim/tables/devicetypes.py +1 -1
  47. nautobot/dcim/templates/dcim/device/base.html +1 -1
  48. nautobot/dcim/templates/dcim/device.html +15 -3
  49. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  50. nautobot/dcim/templates/dcim/moduletype_retrieve.html +17 -0
  51. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +15 -3
  52. nautobot/dcim/tests/test_api.py +2 -0
  53. nautobot/dcim/tests/test_filters.py +14 -7
  54. nautobot/dcim/tests/test_forms.py +54 -0
  55. nautobot/dcim/tests/test_models.py +40 -1
  56. nautobot/dcim/tests/test_views.py +45 -2
  57. nautobot/dcim/utils.py +9 -6
  58. nautobot/dcim/views.py +4 -1
  59. nautobot/extras/api/serializers.py +2 -1
  60. nautobot/extras/api/views.py +7 -59
  61. nautobot/extras/factory.py +50 -12
  62. nautobot/extras/filters/__init__.py +18 -3
  63. nautobot/extras/forms/base.py +10 -4
  64. nautobot/extras/forms/forms.py +7 -0
  65. nautobot/extras/forms/mixins.py +2 -2
  66. nautobot/extras/homepage.py +12 -2
  67. nautobot/extras/jobs.py +2 -2
  68. nautobot/extras/management/__init__.py +3 -0
  69. nautobot/extras/migrations/0111_metadata.py +4 -4
  70. nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
  71. nautobot/extras/migrations/0115_scheduledjob_time_zone.py +23 -0
  72. nautobot/extras/models/customfields.py +54 -0
  73. nautobot/extras/models/jobs.py +105 -9
  74. nautobot/extras/models/metadata.py +18 -18
  75. nautobot/extras/models/models.py +2 -0
  76. nautobot/extras/signals.py +14 -1
  77. nautobot/extras/tables.py +77 -18
  78. nautobot/extras/templates/extras/computedfield.html +4 -0
  79. nautobot/extras/templates/extras/job_detail.html +11 -0
  80. nautobot/extras/templates/extras/scheduledjob.html +13 -2
  81. nautobot/extras/tests/test_api.py +33 -27
  82. nautobot/extras/tests/test_filters.py +57 -1
  83. nautobot/extras/tests/test_jobs.py +2 -2
  84. nautobot/extras/tests/test_models.py +319 -19
  85. nautobot/extras/tests/test_views.py +26 -5
  86. nautobot/extras/utils.py +35 -6
  87. nautobot/extras/views.py +35 -51
  88. nautobot/ipam/api/views.py +9 -2
  89. nautobot/ipam/choices.py +17 -0
  90. nautobot/ipam/factory.py +6 -0
  91. nautobot/ipam/filters.py +2 -2
  92. nautobot/ipam/forms.py +6 -4
  93. nautobot/ipam/migrations/0048_vrf_status.py +23 -0
  94. nautobot/ipam/migrations/0049_vrf_data_migration.py +25 -0
  95. nautobot/ipam/models.py +11 -20
  96. nautobot/ipam/querysets.py +26 -0
  97. nautobot/ipam/tables.py +7 -2
  98. nautobot/ipam/templates/ipam/vrf.html +4 -0
  99. nautobot/ipam/templates/ipam/vrf_edit.html +1 -0
  100. nautobot/ipam/tests/test_api.py +33 -3
  101. nautobot/ipam/tests/test_models.py +89 -2
  102. nautobot/ipam/tests/test_views.py +3 -0
  103. nautobot/ipam/views.py +10 -15
  104. nautobot/project-static/css/base.css +7 -0
  105. nautobot/project-static/docs/404.html +18 -18
  106. nautobot/project-static/docs/apps/index.html +18 -18
  107. nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
  108. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  109. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  110. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
  111. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
  112. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +66 -18
  113. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
  114. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
  115. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
  116. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
  117. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
  118. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +66 -18
  119. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +34 -18
  120. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +82 -63
  121. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +75 -111
  122. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
  123. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +34 -18
  124. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +161 -18
  125. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
  126. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
  127. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
  128. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
  129. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
  130. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
  131. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +21 -19
  132. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +34 -18
  133. nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
  134. nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
  135. nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
  136. nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
  137. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
  138. nautobot/project-static/docs/development/apps/api/models/index.html +33 -22
  139. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
  140. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
  141. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
  142. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
  143. nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
  144. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
  145. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
  146. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
  147. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
  148. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
  149. nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
  150. nautobot/project-static/docs/development/apps/api/setup.html +18 -18
  151. nautobot/project-static/docs/development/apps/api/testing.html +18 -18
  152. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
  153. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
  154. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
  155. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
  156. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
  157. nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
  158. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
  159. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
  160. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
  161. nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
  162. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
  163. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
  164. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
  165. nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
  166. nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
  167. nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
  168. nautobot/project-static/docs/development/apps/index.html +18 -18
  169. nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
  170. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
  171. nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
  172. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
  173. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
  174. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
  175. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
  176. nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
  177. nautobot/project-static/docs/development/core/application-registry.html +18 -18
  178. nautobot/project-static/docs/development/core/best-practices.html +18 -18
  179. nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
  180. nautobot/project-static/docs/development/core/caching.html +18 -18
  181. nautobot/project-static/docs/development/core/controllers.html +18 -18
  182. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
  183. nautobot/project-static/docs/development/core/generic-views.html +18 -18
  184. nautobot/project-static/docs/development/core/getting-started.html +18 -18
  185. nautobot/project-static/docs/development/core/homepage.html +18 -18
  186. nautobot/project-static/docs/development/core/index.html +29 -18
  187. nautobot/project-static/docs/development/core/model-checklist.html +26 -20
  188. nautobot/project-static/docs/development/core/model-features.html +18 -18
  189. nautobot/project-static/docs/development/core/natural-keys.html +18 -18
  190. nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
  191. nautobot/project-static/docs/development/core/release-checklist.html +18 -18
  192. nautobot/project-static/docs/development/core/role-internals.html +18 -18
  193. nautobot/project-static/docs/development/core/settings.html +18 -18
  194. nautobot/project-static/docs/development/core/style-guide.html +19 -19
  195. nautobot/project-static/docs/development/core/templates.html +18 -18
  196. nautobot/project-static/docs/development/core/testing.html +18 -18
  197. nautobot/project-static/docs/development/core/user-preferences.html +18 -18
  198. nautobot/project-static/docs/development/index.html +18 -18
  199. nautobot/project-static/docs/development/jobs/index.html +393 -379
  200. nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
  201. nautobot/project-static/docs/index.html +9032 -13
  202. nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
  203. nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
  204. nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
  205. nautobot/project-static/docs/objects.inv +0 -0
  206. nautobot/project-static/docs/overview/application_stack.html +18 -18
  207. nautobot/project-static/docs/overview/design_philosophy.html +20 -20
  208. nautobot/project-static/docs/overview/index.html +13 -9032
  209. nautobot/project-static/docs/release-notes/index.html +252 -19
  210. nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
  211. nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
  212. nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
  213. nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
  214. nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
  215. nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
  216. nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
  217. nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
  218. nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
  219. nautobot/project-static/docs/release-notes/version-2.2.html +248 -111
  220. nautobot/project-static/docs/release-notes/version-2.3.html +775 -91
  221. nautobot/project-static/docs/requirements.txt +3 -3
  222. nautobot/project-static/docs/search/search_index.json +1 -1
  223. nautobot/project-static/docs/sitemap.xml +278 -278
  224. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  225. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
  226. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
  227. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
  228. nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
  229. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +55 -23
  230. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
  231. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
  232. nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
  233. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +22 -18
  234. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
  235. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
  236. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
  237. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
  238. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
  239. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
  240. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
  241. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
  242. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +69 -82
  243. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
  244. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -52
  245. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +80 -87
  246. nautobot/project-static/docs/user-guide/administration/installation/services.html +37 -44
  247. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
  248. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
  249. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
  250. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
  251. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
  252. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +76 -24
  253. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
  254. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
  255. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
  256. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
  257. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
  258. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
  259. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
  260. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
  261. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
  262. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
  263. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
  264. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
  265. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
  266. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
  267. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
  268. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
  269. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
  270. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
  271. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
  272. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
  273. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
  274. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
  316. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +62 -18
  317. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
  318. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
  319. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
  320. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
  321. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
  322. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
  323. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
  324. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
  325. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
  326. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
  327. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
  328. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
  329. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
  330. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
  331. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
  332. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
  333. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
  334. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
  335. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
  336. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
  337. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
  338. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
  339. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
  340. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
  341. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
  342. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
  343. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
  344. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
  345. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
  346. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
  347. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
  348. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
  349. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
  350. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
  351. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
  352. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
  353. nautobot/project-static/docs/user-guide/index.html +18 -18
  354. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
  355. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
  356. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
  357. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
  358. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
  359. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
  360. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
  361. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
  362. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
  363. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
  364. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
  365. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
  366. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
  367. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
  368. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
  369. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
  370. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
  371. nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
  372. nautobot/project-static/docs/user-guide/platform-functionality/{metadata.html → objectmetadata.html} +197 -84
  373. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
  374. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
  375. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
  376. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
  377. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
  378. nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
  379. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
  380. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
  381. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
  382. nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
  383. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
  384. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
  385. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
  386. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
  387. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
  388. nautobot/project-static/js/homepage_layout.js +3 -0
  389. nautobot/tenancy/templates/tenancy/tenant.html +4 -4
  390. nautobot/virtualization/models.py +0 -2
  391. nautobot/virtualization/tables.py +2 -5
  392. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/METADATA +3 -3
  393. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/RECORD +397 -393
  394. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  395. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  396. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/LICENSE.txt +0 -0
  397. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/NOTICE +0 -0
  398. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/WHEEL +0 -0
  399. {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/entry_points.txt +0 -0
@@ -261,7 +261,7 @@ class ModuleTable(StatusTableMixin, RoleTableMixin, BaseTable):
261
261
  )
262
262
  location = tables.Column(linkify=True)
263
263
  tenant = TenantColumn()
264
- tags = TagColumn(url_name="dcim:device_list")
264
+ tags = TagColumn(url_name="dcim:module_list")
265
265
  actions = ButtonsColumn(Module, prepend_template=MODULE_BUTTONS)
266
266
 
267
267
  class Meta(BaseTable.Meta):
@@ -396,7 +396,7 @@ class DeviceModuleConsolePortTable(ConsolePortTable):
396
396
  "actions",
397
397
  )
398
398
  row_attrs = {
399
- "style": cable_status_color_css,
399
+ "class": cable_status_color_css,
400
400
  }
401
401
 
402
402
 
@@ -460,7 +460,7 @@ class DeviceModuleConsoleServerPortTable(ConsoleServerPortTable):
460
460
  "actions",
461
461
  )
462
462
  row_attrs = {
463
- "style": cable_status_color_css,
463
+ "class": cable_status_color_css,
464
464
  }
465
465
 
466
466
 
@@ -535,7 +535,7 @@ class DeviceModulePowerPortTable(PowerPortTable):
535
535
  "connection",
536
536
  "actions",
537
537
  )
538
- row_attrs = {"style": cable_status_color_css}
538
+ row_attrs = {"class": cable_status_color_css}
539
539
 
540
540
 
541
541
  class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
@@ -613,7 +613,7 @@ class DeviceModulePowerOutletTable(PowerOutletTable):
613
613
  "connection",
614
614
  "actions",
615
615
  )
616
- row_attrs = {"style": cable_status_color_css}
616
+ row_attrs = {"class": cable_status_color_css}
617
617
 
618
618
 
619
619
  class BaseInterfaceTable(BaseTable):
@@ -739,7 +739,7 @@ class DeviceModuleInterfaceTable(InterfaceTable):
739
739
  "actions",
740
740
  ]
741
741
  row_attrs = {
742
- "style": cable_status_color_css,
742
+ "class": cable_status_color_css,
743
743
  "data-name": lambda record: record.name,
744
744
  }
745
745
 
@@ -815,7 +815,7 @@ class DeviceModuleFrontPortTable(FrontPortTable):
815
815
  "cable_peer",
816
816
  "actions",
817
817
  )
818
- row_attrs = {"style": cable_status_color_css}
818
+ row_attrs = {"class": cable_status_color_css}
819
819
 
820
820
 
821
821
  class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
@@ -874,7 +874,7 @@ class DeviceModuleRearPortTable(RearPortTable):
874
874
  "cable_peer",
875
875
  "actions",
876
876
  )
877
- row_attrs = {"style": cable_status_color_css}
877
+ row_attrs = {"class": cable_status_color_css}
878
878
 
879
879
 
880
880
  class DeviceBayTable(DeviceComponentTable):
@@ -1125,13 +1125,27 @@ class DeviceRedundancyGroupTable(BaseTable):
1125
1125
  url_params={"device_redundancy_group": "pk"},
1126
1126
  verbose_name="Devices",
1127
1127
  )
1128
+ controller_count = LinkedCountColumn(
1129
+ viewname="dcim:controller_list",
1130
+ url_params={"controller_device_redundancy_group": "pk"},
1131
+ verbose_name="Controllers",
1132
+ )
1128
1133
  secrets_group = tables.Column(linkify=True)
1129
1134
  tags = TagColumn(url_name="dcim:deviceredundancygroup_list")
1130
1135
 
1131
1136
  class Meta(BaseTable.Meta):
1132
1137
  model = DeviceRedundancyGroup
1133
- fields = ("pk", "name", "status", "failover_strategy", "device_count", "secrets_group", "tags")
1134
- default_columns = ("pk", "name", "status", "failover_strategy", "device_count")
1138
+ fields = (
1139
+ "pk",
1140
+ "name",
1141
+ "status",
1142
+ "failover_strategy",
1143
+ "controller_count",
1144
+ "device_count",
1145
+ "secrets_group",
1146
+ "tags",
1147
+ )
1148
+ default_columns = ("pk", "name", "status", "failover_strategy", "controller_count", "device_count")
1135
1149
 
1136
1150
 
1137
1151
  #
@@ -160,7 +160,7 @@ class ModuleTypeTable(BaseTable):
160
160
  url_params={"module_type": "pk"},
161
161
  verbose_name="Modules",
162
162
  )
163
- tags = TagColumn(url_name="dcim:devicetype_list")
163
+ tags = TagColumn(url_name="dcim:moduletype_list")
164
164
 
165
165
  class Meta(BaseTable.Meta):
166
166
  model = ModuleType
@@ -71,7 +71,7 @@
71
71
  <a href="{% url 'dcim:device_modulebays' pk=object.pk %}">Modules {% badge module_count %}</a>
72
72
  </li>
73
73
  {% endif %}
74
- {% with interface_count=object.all_interfaces.count %}
74
+ {% with interface_count=object.vc_interfaces.count %}
75
75
  {% if interface_count %}
76
76
  <li role="presentation" {% if active_tab == 'interfaces' %} class="active"{% endif %}>
77
77
  <a href="{% url 'dcim:device_interfaces' pk=object.pk %}">Interfaces {% badge interface_count %}</a>
@@ -233,7 +233,7 @@
233
233
  {% include 'dcim/inc/detail_softwareversion_softwareimagefile_rows.html' %}
234
234
  </table>
235
235
  </div>
236
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields_advanced_ui=False %}
236
+ {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields=object.get_computed_fields_grouping_basic computed_fields_advanced_ui=False %}
237
237
  {% include 'inc/relationships/panel_override.html' with relationships_fields_override=object.get_relationships_data_basic_fields %}
238
238
  {% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:device_list' %}
239
239
  <div class="panel panel-default">
@@ -406,6 +406,18 @@
406
406
  {% endif %}
407
407
  {% if object.is_dynamic_group_associable_model and perms.extras.view_dynamicgroup %}
408
408
  <div id="dynamic_groups" role="tabpanel" class="tab-pane {% if request.GET.tab == 'dynamic_groups' %}active{% else %}fade{% endif %}">
409
+ <div class="row">
410
+ <div class="col-md-12">
411
+ <div class="alert alert-warning">
412
+ Dynamic group membership is cached for performance reasons,
413
+ therefore this table may not always be up-to-date.
414
+ <br>You can refresh the membership of any specific group by navigating to it from the list below
415
+ or from the <a href="{% url 'extras:dynamicgroup_list' %}">Dynamic Groups list view</a>.
416
+ <br>You can also refresh the membership of all groups by running the
417
+ <a href="{% url 'extras:job_run_by_class_path' class_path='nautobot.core.jobs.groups.RefreshDynamicGroupCaches' %}">Refresh Dynamic Group Caches job</a>.
418
+ </div>
419
+ </div>
420
+ </div>
409
421
  <div class="row">
410
422
  <div class="col-md-12">
411
423
  <form method="post">
@@ -427,7 +439,7 @@
427
439
  </div>
428
440
  {% endif %}
429
441
  {% if object.is_metadata_associable_model and perms.extras.view_objectmetadata %}
430
- <div id="object_metadatas" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadatas' %}active{% else %}fade{% endif %}">
442
+ <div id="object_metadata" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadata' %}active{% else %}fade{% endif %}">
431
443
  <div class="row">
432
444
  <div class="col-md-12">
433
445
  <form method="post">
@@ -440,7 +452,7 @@
440
452
  </div>
441
453
  </div>
442
454
  <div class="table-responsive">
443
- {% render_table associated_object_metadatas_table 'inc/table.html' %}
455
+ {% render_table associated_object_metadata_table 'inc/table.html' %}
444
456
  </div>
445
457
  </div>
446
458
  </form>
@@ -45,6 +45,12 @@
45
45
  {% endblock content_right_page %}
46
46
 
47
47
  {% block content_full_width_page %}
48
+ <div class="panel panel-default">
49
+ <div class="panel-heading">
50
+ <strong>Controllers</strong>
51
+ </div>
52
+ {% include 'responsive_table.html' with table=controllers_table %}
53
+ </div>
48
54
  <div class="panel panel-default">
49
55
  <div class="panel-heading">
50
56
  <strong>Devices</strong>
@@ -113,6 +113,23 @@
113
113
  </div>
114
114
  {% endblock content_left_page %}
115
115
 
116
+ {% block content_right_page %}
117
+ <div class="panel panel-default">
118
+ <div class="panel-heading">
119
+ <strong>Comments</strong>
120
+ </div>
121
+ <div class="panel-body rendered-markdown">
122
+ {% if object.comments %}
123
+ {{ object.comments|render_markdown }}
124
+ {% else %}
125
+ <span class="text-muted">None</span>
126
+ {% endif %}
127
+ </div>
128
+ </div>
129
+ {% endblock content_right_page %}
130
+
131
+
132
+
116
133
  {% block extra_tab_content %}
117
134
  <div role="tabpanel" class="tab-pane {% if request.GET.tab == 'interfaces' %}active{% else %}fade{% endif %}" id="interfaces">
118
135
  {% include 'dcim/inc/moduletype_component_table.html' with table=interface_table title='Interfaces' tab='interfaces' %}
@@ -144,7 +144,7 @@
144
144
  </div>
145
145
  <div class="row">
146
146
  <div class="col-md-6">
147
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields_advanced_ui=False %}
147
+ {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic computed_fields=object.get_computed_fields_grouping_basic computed_fields_advanced_ui=False %}
148
148
  {% include 'inc/relationships_panel.html' %}
149
149
  {% include 'extras/inc/tags_panel.html' %}
150
150
  {% plugin_left_page object %}
@@ -215,6 +215,18 @@
215
215
  {% endif %}
216
216
  {% if object.is_dynamic_group_associable_model and perms.extras.view_dynamicgroup %}
217
217
  <div id="dynamic_groups" role="tabpanel" class="tab-pane {% if request.GET.tab == 'dynamic_groups' %}active{% else %}fade{% endif %}">
218
+ <div class="row">
219
+ <div class="col-md-12">
220
+ <div class="alert alert-warning">
221
+ Dynamic group membership is cached for performance reasons,
222
+ therefore this table may not always be up-to-date.
223
+ <br>You can refresh the membership of any specific group by navigating to it from the list below
224
+ or from the <a href="{% url 'extras:dynamicgroup_list' %}">Dynamic Groups list view</a>.
225
+ <br>You can also refresh the membership of all groups by running the
226
+ <a href="{% url 'extras:job_run_by_class_path' class_path='nautobot.core.jobs.groups.RefreshDynamicGroupCaches' %}">Refresh Dynamic Group Caches job</a>.
227
+ </div>
228
+ </div>
229
+ </div>
218
230
  <div class="row">
219
231
  <div class="col-md-12">
220
232
  <form method="post">
@@ -236,7 +248,7 @@
236
248
  </div>
237
249
  {% endif %}
238
250
  {% if object.is_metadata_associable_model and perms.extras.view_objectmetadata %}
239
- <div id="object_metadatas" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadatas' %}active{% else %}fade{% endif %}">
251
+ <div id="object_metadata" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadata' %}active{% else %}fade{% endif %}">
240
252
  <div class="row">
241
253
  <div class="col-md-12">
242
254
  <form method="post">
@@ -249,7 +261,7 @@
249
261
  </div>
250
262
  </div>
251
263
  <div class="table-responsive">
252
- {% render_table associated_object_metadatas_table 'inc/table.html' %}
264
+ {% render_table associated_object_metadata_table 'inc/table.html' %}
253
265
  </div>
254
266
  </div>
255
267
  </form>
@@ -1022,6 +1022,7 @@ class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
1022
1022
  model = ModuleType
1023
1023
  bulk_update_data = {
1024
1024
  "part_number": "ABC123",
1025
+ "comments": "changed comment",
1025
1026
  }
1026
1027
 
1027
1028
  @classmethod
@@ -1033,6 +1034,7 @@ class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
1033
1034
  "manufacturer": manufacturer_id,
1034
1035
  "model": "Module Type 1",
1035
1036
  "part_number": "123456",
1037
+ "comments": "test comment",
1036
1038
  },
1037
1039
  {
1038
1040
  "manufacturer": manufacturer_id,
@@ -656,9 +656,15 @@ def common_test_data(cls):
656
656
  device_redundancy_groups = iter(DeviceRedundancyGroup.objects.all())
657
657
 
658
658
  module_types = (
659
- ModuleType.objects.create(manufacturer=cls.manufacturers[0], model="Filter Test Module Type 1"),
660
- ModuleType.objects.create(manufacturer=cls.manufacturers[1], model="Filter Test Module Type 2"),
661
- ModuleType.objects.create(manufacturer=cls.manufacturers[2], model="Filter Test Module Type 3"),
659
+ ModuleType.objects.create(
660
+ manufacturer=cls.manufacturers[0], model="Filter Test Module Type 1", comments="Module Type 1"
661
+ ),
662
+ ModuleType.objects.create(
663
+ manufacturer=cls.manufacturers[1], model="Filter Test Module Type 2", comments="Module Type 2"
664
+ ),
665
+ ModuleType.objects.create(
666
+ manufacturer=cls.manufacturers[2], model="Filter Test Module Type 3", comments="Module Type 3"
667
+ ),
662
668
  )
663
669
 
664
670
  # Create 3 of each component template on the first two module types
@@ -898,7 +904,7 @@ class ModularDeviceComponentTestMixin(DeviceComponentTestMixin):
898
904
  )
899
905
  parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
900
906
  module_type = ModuleType.objects.create(
901
- manufacturer=manufacturer, model=f"Test Device Filter for {model} Module Type"
907
+ manufacturer=manufacturer, model=f"Test Device Filter for {model} Module Type", comments="Module Type test"
902
908
  )
903
909
  module = Module.objects.create(
904
910
  module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
@@ -2526,7 +2532,7 @@ class InterfaceTestCase(PathEndpointModelTestMixin, ModularDeviceComponentTestMi
2526
2532
  name="Parent module bay", position="1", parent_device=device_vc_master
2527
2533
  )
2528
2534
  module_type = ModuleType.objects.create(
2529
- manufacturer=manufacturer, model="Test Device Filter for Interface Module Type"
2535
+ manufacturer=manufacturer, model="Test Device Filter for Interface Module Type", comments="Module Type test"
2530
2536
  )
2531
2537
  module = Module.objects.create(
2532
2538
  module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
@@ -2772,7 +2778,7 @@ class FrontPortTestCase(ModularDeviceComponentTestMixin, FilterTestCases.FilterT
2772
2778
  )
2773
2779
  parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
2774
2780
  module_type = ModuleType.objects.create(
2775
- manufacturer=manufacturer, model="Test Device Filter for FrontPort Module Type"
2781
+ manufacturer=manufacturer, model="Test Device Filter for FrontPort Module Type", comments="Module Type test"
2776
2782
  )
2777
2783
  module = Module.objects.create(
2778
2784
  module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
@@ -3357,7 +3363,7 @@ class CableTestCase(FilterTestCases.FilterTestCase):
3357
3363
  )
3358
3364
  parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
3359
3365
  module_type = ModuleType.objects.create(
3360
- manufacturer=manufacturer, model="Test Device Filter for Cable Module Type"
3366
+ manufacturer=manufacturer, model="Test Device Filter for Cable Module Type", comments="Module Type test"
3361
3367
  )
3362
3368
  module = Module.objects.create(
3363
3369
  module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
@@ -4012,6 +4018,7 @@ class ModuleTypeTestCase(FilterTestCases.FilterTestCase):
4012
4018
  queryset = ModuleType.objects.all()
4013
4019
  filterset = ModuleTypeFilterSet
4014
4020
  generic_filter_tests = [
4021
+ ("comments",),
4015
4022
  ("manufacturer", "manufacturer__id"),
4016
4023
  ("manufacturer", "manufacturer__name"),
4017
4024
  ("model",),
@@ -12,6 +12,8 @@ from nautobot.dcim.models import (
12
12
  Manufacturer,
13
13
  Platform,
14
14
  Rack,
15
+ SoftwareImageFile,
16
+ SoftwareVersion,
15
17
  )
16
18
  from nautobot.extras.models import Role, SecretsGroup, Status
17
19
  from nautobot.ipam.models import VLAN
@@ -43,6 +45,15 @@ class DeviceTestCase(FormTestCases.BaseFormTestCase):
43
45
  cls.manufacturer = cls.device_type.manufacturer
44
46
  cls.platform = Platform.objects.filter(manufacturer=cls.device_type.manufacturer).first()
45
47
  cls.device_role = Role.objects.get_for_model(Device).first()
48
+ cls.software_version_contains_no_valid_image_for_device_type = SoftwareVersion.objects.create(
49
+ platform=cls.platform,
50
+ version="New version 1.0.0",
51
+ status=Status.objects.get_for_model(SoftwareVersion).first(),
52
+ )
53
+ cls.software_version = SoftwareVersion.objects.first()
54
+ cls.software_image_files = SoftwareImageFile.objects.exclude(software_version=cls.software_version).exclude(
55
+ default_image=True
56
+ )
46
57
 
47
58
  Device.objects.create(
48
59
  name="Device 1",
@@ -134,6 +145,49 @@ class DeviceTestCase(FormTestCases.BaseFormTestCase):
134
145
  self.assertFalse(form.is_valid())
135
146
  self.assertIn("face", form.errors)
136
147
 
148
+ def test_no_software_image_file_specified_is_valid(self):
149
+ form = DeviceForm(
150
+ data={
151
+ "name": "New Device",
152
+ "role": self.device_role.pk,
153
+ "tenant": None,
154
+ "manufacturer": self.manufacturer.pk,
155
+ "device_type": self.device_type.pk,
156
+ "location": self.location.pk,
157
+ "rack": None,
158
+ "face": None,
159
+ "position": None,
160
+ "platform": self.platform.pk,
161
+ "status": self.device_status.pk,
162
+ "secrets_group": SecretsGroup.objects.first().pk,
163
+ "software_version": self.software_version_contains_no_valid_image_for_device_type.pk,
164
+ "software_image_files": [],
165
+ }
166
+ )
167
+ self.assertTrue(form.is_valid())
168
+
169
+ def test_invalid_software_image_file_specified(self):
170
+ form = DeviceForm(
171
+ data={
172
+ "name": "New Device",
173
+ "role": self.device_role.pk,
174
+ "tenant": None,
175
+ "manufacturer": self.manufacturer.pk,
176
+ "device_type": self.device_type.pk,
177
+ "location": self.location.pk,
178
+ "rack": None,
179
+ "face": None,
180
+ "position": None,
181
+ "platform": self.platform.pk,
182
+ "status": self.device_status.pk,
183
+ "secrets_group": SecretsGroup.objects.first().pk,
184
+ "software_version": self.software_version.pk,
185
+ "software_image_files": list(self.software_image_files.values_list("pk", flat=True)),
186
+ }
187
+ )
188
+ self.assertFalse(form.is_valid())
189
+ self.assertIn("software_image_files", form.errors)
190
+
137
191
  def test_non_racked_device_with_position(self):
138
192
  form = DeviceForm(
139
193
  data={
@@ -1723,11 +1723,19 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
1723
1723
  software_version.software_image_files.all().update(default_image=False)
1724
1724
  self.device_type.software_image_files.set([])
1725
1725
  self.device.software_version = software_version
1726
+ invalid_software_image_file = SoftwareImageFile.objects.filter(default_image=False).first()
1727
+ invalid_software_image_file.device_types.set([])
1728
+ self.device.software_image_files.set([invalid_software_image_file])
1726
1729
 
1727
- # No device type or default image match
1730
+ # There is an invalid non-default software_image_file specified for the software version
1731
+ # It is not a default image and it does not match any device type
1728
1732
  with self.assertRaises(ValidationError):
1729
1733
  self.device.validated_save()
1730
1734
 
1735
+ # user should be able to specify any software version without specifying software_image_files
1736
+ self.device.software_image_files.set([])
1737
+ self.device.validated_save()
1738
+
1731
1739
  # Default image matches
1732
1740
  software_image_file = software_version.software_image_files.first()
1733
1741
  software_image_file.default_image = True
@@ -1903,6 +1911,37 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
1903
1911
  self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
1904
1912
 
1905
1913
 
1914
+ class DeviceBayTestCase(ModelTestCases.BaseModelTestCase):
1915
+ model = DeviceBay
1916
+
1917
+ def setUp(self):
1918
+ self.devices = Device.objects.filter(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
1919
+ devicetype = DeviceType.objects.create(
1920
+ manufacturer=self.devices[0].device_type.manufacturer,
1921
+ model="TestDeviceType1",
1922
+ u_height=0,
1923
+ subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
1924
+ )
1925
+ child_device = Device.objects.create(
1926
+ device_type=devicetype,
1927
+ role=self.devices[0].role,
1928
+ name="TestDevice1",
1929
+ status=self.devices[0].status,
1930
+ location=self.devices[0].location,
1931
+ )
1932
+ DeviceBay.objects.create(device=self.devices[0], name="Device Bay 1", installed_device=child_device)
1933
+
1934
+ def test_assigning_installed_device(self):
1935
+ server = Device.objects.exclude(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD).last()
1936
+ bay = DeviceBay(device=self.devices[1], name="Device Bay Err", installed_device=server)
1937
+ with self.assertRaises(ValidationError) as err:
1938
+ bay.validated_save()
1939
+ self.assertIn(
1940
+ f'Cannot install device "{server}"; device-type "{server.device_type}" subdevice_role is not "child".',
1941
+ str(err.exception),
1942
+ )
1943
+
1944
+
1906
1945
  class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
1907
1946
  model = DeviceTypeToSoftwareImageFile
1908
1947
 
@@ -817,6 +817,7 @@ class DeviceTypeTestCase(
817
817
  cls.bulk_edit_data = {
818
818
  "u_height": 0,
819
819
  "is_full_depth": False,
820
+ "comments": "changed comment",
820
821
  }
821
822
 
822
823
  def test_list_has_correct_links(self):
@@ -1196,6 +1197,7 @@ class ModuleTypeTestCase(
1196
1197
  ModuleType.objects.create(
1197
1198
  model="Test Module Type 1",
1198
1199
  manufacturer=manufacturers[0],
1200
+ comments="test comment",
1199
1201
  )
1200
1202
  ModuleType.objects.create(
1201
1203
  model="Test Module Type 2",
@@ -1215,10 +1217,12 @@ class ModuleTypeTestCase(
1215
1217
  "model": "Test Module Type X",
1216
1218
  "part_number": "123ABC",
1217
1219
  "tags": [t.pk for t in Tag.objects.get_for_model(ModuleType)],
1220
+ "comments": "test comment",
1218
1221
  }
1219
1222
 
1220
1223
  cls.bulk_edit_data = {
1221
1224
  "manufacturer": manufacturers[1].pk,
1225
+ "comments": "changed comment",
1222
1226
  }
1223
1227
 
1224
1228
  def test_list_has_correct_links(self):
@@ -2080,7 +2084,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2080
2084
  status_active = statuses[0]
2081
2085
 
2082
2086
  # We want unique sets of software image files for each device type
2083
- software_image_files = list(SoftwareImageFile.objects.all()[:4])
2087
+ software_image_files = list(SoftwareImageFile.objects.filter(default_image=False)[:4])
2084
2088
  software_versions = list(SoftwareVersion.objects.filter(software_image_files__isnull=False)[:2])
2085
2089
  software_image_files[0].software_version = software_versions[0]
2086
2090
  software_image_files[1].software_version = software_versions[0]
@@ -2090,6 +2094,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2090
2094
  software_image_file.save()
2091
2095
  devicetypes[0].software_image_files.set(software_image_files[:2])
2092
2096
  devicetypes[1].software_image_files.set(software_image_files[2:])
2097
+ # Only valid software image files are those that belong to the device type or default images
2098
+ valid_software_image_files = software_image_files[2:] + [
2099
+ SoftwareImageFile.objects.filter(default_image=True).first()
2100
+ ]
2093
2101
 
2094
2102
  cls.custom_fields = (
2095
2103
  CustomField.objects.create(
@@ -2201,7 +2209,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2201
2209
  "cf_crash_counter": -1,
2202
2210
  "cr_router-id": None,
2203
2211
  "software_version": software_versions[1].pk,
2204
- "software_image_files": [f.pk for f in software_versions[0].software_image_files.all()],
2212
+ "software_image_files": [f.pk for f in valid_software_image_files],
2205
2213
  }
2206
2214
 
2207
2215
  cls.bulk_edit_data = {
@@ -2359,6 +2367,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
2359
2367
  sorted(interface_ips),
2360
2368
  )
2361
2369
 
2370
+ with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
2371
+ assign_ip_form_data["pk"] = []
2372
+ assign_ip_request = {
2373
+ "path": reverse("ipam:ipaddress_assign")
2374
+ + f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
2375
+ "data": post_data(assign_ip_form_data),
2376
+ }
2377
+ response = self.client.post(**assign_ip_request, follow=True)
2378
+ self.assertHttpStatus(response, 200)
2379
+ self.assertIn(
2380
+ "Please select at least one IP Address from the table.", response.content.decode(response.charset)
2381
+ )
2382
+
2362
2383
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
2363
2384
  def test_device_rearports(self):
2364
2385
  device = Device.objects.first()
@@ -4172,6 +4193,28 @@ class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
4172
4193
  "domain": "domain-x",
4173
4194
  }
4174
4195
 
4196
+ def test_device_interfaces_count_correct(self):
4197
+ """
4198
+ This checks whether the other memebers' interfaces are included in the
4199
+ interfaces tab of the master device and whether the interface count on the tab header is
4200
+ rendered correctly.
4201
+ """
4202
+ self.user.is_superuser = True
4203
+ self.user.save()
4204
+ interface_status = Status.objects.get_for_model(Interface).first()
4205
+ Interface.objects.create(device=self.devices[0], name="eth0", status=interface_status)
4206
+ Interface.objects.create(device=self.devices[0], name="eth1", status=interface_status)
4207
+ Interface.objects.create(device=self.devices[1], name="device 1 interface 1", status=interface_status)
4208
+ Interface.objects.create(device=self.devices[1], name="device 1 interface 2", status=interface_status)
4209
+ Interface.objects.create(device=self.devices[2], name="device 2 interface 1", status=interface_status)
4210
+ Interface.objects.create(device=self.devices[2], name="device 2 interface 2", status=interface_status)
4211
+ response = self.client.get(reverse("dcim:device_interfaces", kwargs={"pk": self.devices[0].pk}))
4212
+ self.assertIn('Interfaces <span class="badge">6</span>', str(response.content))
4213
+ self.assertIn("device 1 interface 1", str(response.content))
4214
+ self.assertIn("device 1 interface 2", str(response.content))
4215
+ self.assertIn("device 2 interface 1", str(response.content))
4216
+ self.assertIn("device 2 interface 2", str(response.content))
4217
+
4175
4218
  def test_device_column_visible(self):
4176
4219
  """
4177
4220
  This checks whether the device column on a device's interfaces
nautobot/dcim/utils.py CHANGED
@@ -14,7 +14,7 @@ from netutils.lib_mapper import (
14
14
  SCRAPLI_LIB_MAPPER_REVERSE,
15
15
  )
16
16
 
17
- from nautobot.core.utils.color import hex_to_rgb, lighten_color, rgb_to_hex
17
+ from nautobot.core.choices import ColorChoices
18
18
  from nautobot.core.utils.config import get_settings_or_config
19
19
  from nautobot.dcim.choices import InterfaceModeChoices
20
20
  from nautobot.dcim.constants import NETUTILS_NETWORK_DRIVER_MAPPING_NAMES
@@ -55,11 +55,14 @@ def cable_status_color_css(record):
55
55
  """
56
56
  if not record.cable:
57
57
  return ""
58
- # The status colors are for use with labels and such, and tend to be quite bright.
59
- # For this function we want a much milder, mellower color suitable as a row background.
60
- base_color = record.cable.get_status_color().strip("#")
61
- lighter_color = rgb_to_hex(*lighten_color(*hex_to_rgb(base_color), 0.75))
62
- return f"background-color: #{lighter_color}"
58
+ else:
59
+ CABLE_STATUS_TO_CSS_CLASS = {
60
+ ColorChoices.COLOR_GREEN: "success",
61
+ ColorChoices.COLOR_AMBER: "warning",
62
+ ColorChoices.COLOR_CYAN: "info",
63
+ }
64
+ status_color = record.cable.get_status_color().strip("#")
65
+ return CABLE_STATUS_TO_CSS_CLASS.get(status_color, "")
63
66
 
64
67
 
65
68
  def get_network_driver_mapping_tool_names():
nautobot/dcim/views.py CHANGED
@@ -1923,7 +1923,7 @@ class DeviceInterfacesView(DeviceComponentTabView):
1923
1923
 
1924
1924
  def get_extra_context(self, request, instance):
1925
1925
  interfaces = (
1926
- instance.all_interfaces.restrict(request.user, "view")
1926
+ instance.vc_interfaces.restrict(request.user, "view")
1927
1927
  .prefetch_related(
1928
1928
  Prefetch("ip_addresses", queryset=IPAddress.objects.restrict(request.user)),
1929
1929
  Prefetch("member_interfaces", queryset=Interface.objects.restrict(request.user)),
@@ -4049,6 +4049,9 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
4049
4049
  devices_table = tables.DeviceTable(devices)
4050
4050
  devices_table.columns.show("device_redundancy_group_priority")
4051
4051
  context["devices_table"] = devices_table
4052
+ controllers = instance.controllers_sorted.restrict(request.user)
4053
+ controllers_table = tables.ControllerTable(controllers)
4054
+ context["controllers_table"] = controllers_table
4052
4055
  return context
4053
4056
 
4054
4057
 
@@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
6
6
  from drf_spectacular.utils import extend_schema_field
7
7
  from rest_framework import serializers
8
8
  from rest_framework.validators import UniqueTogetherValidator
9
+ from timezone_field.rest_framework import TimeZoneSerializerField
9
10
 
10
11
  from nautobot.core.api import (
11
12
  BaseModelSerializer,
@@ -228,7 +229,6 @@ class ContactAssociationSerializer(NautobotModelSerializer):
228
229
  fields = "__all__"
229
230
  validators = []
230
231
  extra_kwargs = {
231
- "role": {"required": False},
232
232
  "contact": {"required": False},
233
233
  "team": {"required": False},
234
234
  }
@@ -582,6 +582,7 @@ class JobVariableSerializer(serializers.Serializer):
582
582
 
583
583
  class ScheduledJobSerializer(BaseModelSerializer):
584
584
  # start_time = serializers.DateTimeField(format=None, required=False)
585
+ time_zone = TimeZoneSerializerField(required=False)
585
586
 
586
587
  class Meta:
587
588
  model = ScheduledJob