nautobot 2.3.0b1__py3-none-any.whl → 2.3.1__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 (380) 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/filters.py +15 -1
  15. nautobot/core/forms/forms.py +10 -2
  16. nautobot/core/graphql/generators.py +2 -2
  17. nautobot/core/graphql/schema.py +6 -14
  18. nautobot/core/jobs/__init__.py +4 -1
  19. nautobot/core/management/commands/generate_test_data.py +2 -2
  20. nautobot/core/models/__init__.py +2 -2
  21. nautobot/core/settings.py +13 -2
  22. nautobot/core/settings.yaml +16 -2
  23. nautobot/core/tables.py +3 -0
  24. nautobot/core/templates/generic/object_retrieve.html +6 -6
  25. nautobot/core/templates/inc/computed_fields/panel_data.html +36 -24
  26. nautobot/core/templates/inc/object_details_advanced_panel.html +1 -1
  27. nautobot/core/templates/nautobot_config.py.j2 +15 -0
  28. nautobot/core/testing/filters.py +12 -1
  29. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  30. nautobot/core/tests/test_jobs.py +74 -1
  31. nautobot/core/views/__init__.py +1 -1
  32. nautobot/core/views/generic.py +1 -1
  33. nautobot/core/views/mixins.py +1 -1
  34. nautobot/core/views/utils.py +8 -6
  35. nautobot/dcim/factory.py +4 -1
  36. nautobot/dcim/filters/__init__.py +4 -0
  37. nautobot/dcim/forms.py +24 -0
  38. nautobot/dcim/migrations/0061_module_models.py +1 -0
  39. nautobot/dcim/models/device_components.py +7 -0
  40. nautobot/dcim/models/devices.py +18 -19
  41. nautobot/dcim/models/racks.py +0 -1
  42. nautobot/dcim/tables/devices.py +17 -3
  43. nautobot/dcim/tables/devicetypes.py +1 -1
  44. nautobot/dcim/templates/dcim/device/base.html +1 -1
  45. nautobot/dcim/templates/dcim/device.html +3 -3
  46. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  47. nautobot/dcim/templates/dcim/moduletype_retrieve.html +17 -0
  48. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +3 -3
  49. nautobot/dcim/tests/test_api.py +2 -0
  50. nautobot/dcim/tests/test_filters.py +14 -7
  51. nautobot/dcim/tests/test_forms.py +54 -0
  52. nautobot/dcim/tests/test_models.py +40 -1
  53. nautobot/dcim/tests/test_views.py +45 -2
  54. nautobot/dcim/views.py +4 -1
  55. nautobot/extras/api/serializers.py +0 -1
  56. nautobot/extras/api/views.py +7 -59
  57. nautobot/extras/factory.py +50 -12
  58. nautobot/extras/filters/__init__.py +4 -1
  59. nautobot/extras/forms/base.py +10 -4
  60. nautobot/extras/forms/forms.py +1 -0
  61. nautobot/extras/homepage.py +12 -2
  62. nautobot/extras/jobs.py +2 -2
  63. nautobot/extras/migrations/0111_metadata.py +4 -4
  64. nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
  65. nautobot/extras/models/customfields.py +54 -0
  66. nautobot/extras/models/jobs.py +83 -0
  67. nautobot/extras/models/metadata.py +18 -18
  68. nautobot/extras/models/models.py +2 -0
  69. nautobot/extras/signals.py +14 -1
  70. nautobot/extras/tables.py +43 -14
  71. nautobot/extras/templates/extras/computedfield.html +4 -0
  72. nautobot/extras/templates/extras/job_detail.html +11 -0
  73. nautobot/extras/tests/test_api.py +16 -9
  74. nautobot/extras/tests/test_jobs.py +2 -2
  75. nautobot/extras/tests/test_models.py +20 -18
  76. nautobot/extras/tests/test_views.py +23 -3
  77. nautobot/extras/utils.py +35 -6
  78. nautobot/extras/views.py +28 -51
  79. nautobot/ipam/filters.py +1 -1
  80. nautobot/ipam/forms.py +1 -1
  81. nautobot/ipam/models.py +9 -20
  82. nautobot/ipam/querysets.py +26 -0
  83. nautobot/ipam/tables.py +4 -0
  84. nautobot/ipam/tests/test_models.py +89 -2
  85. nautobot/ipam/views.py +10 -15
  86. nautobot/project-static/css/base.css +1 -0
  87. nautobot/project-static/docs/404.html +18 -18
  88. nautobot/project-static/docs/apps/index.html +18 -18
  89. nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
  90. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  91. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  92. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
  93. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
  94. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +66 -18
  95. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
  96. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
  97. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
  98. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
  99. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
  100. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +66 -18
  101. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +34 -18
  102. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +82 -63
  103. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +75 -111
  104. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
  105. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +34 -18
  106. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +161 -18
  107. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
  108. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
  109. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
  110. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
  111. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
  112. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
  113. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +21 -19
  114. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +34 -18
  115. nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
  116. nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
  117. nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
  118. nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
  119. nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
  120. nautobot/project-static/docs/development/apps/api/models/index.html +33 -22
  121. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
  122. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
  123. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
  124. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
  125. nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
  126. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
  127. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
  128. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
  129. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
  130. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
  131. nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
  132. nautobot/project-static/docs/development/apps/api/setup.html +18 -18
  133. nautobot/project-static/docs/development/apps/api/testing.html +18 -18
  134. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
  135. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
  136. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
  137. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
  138. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
  139. nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
  140. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
  141. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
  142. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
  143. nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
  144. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
  145. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
  146. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
  147. nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
  148. nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
  149. nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
  150. nautobot/project-static/docs/development/apps/index.html +18 -18
  151. nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
  152. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
  153. nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
  154. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
  155. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
  156. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
  157. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
  158. nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
  159. nautobot/project-static/docs/development/core/application-registry.html +18 -18
  160. nautobot/project-static/docs/development/core/best-practices.html +18 -18
  161. nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
  162. nautobot/project-static/docs/development/core/caching.html +18 -18
  163. nautobot/project-static/docs/development/core/controllers.html +18 -18
  164. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
  165. nautobot/project-static/docs/development/core/generic-views.html +18 -18
  166. nautobot/project-static/docs/development/core/getting-started.html +18 -18
  167. nautobot/project-static/docs/development/core/homepage.html +18 -18
  168. nautobot/project-static/docs/development/core/index.html +29 -18
  169. nautobot/project-static/docs/development/core/model-checklist.html +26 -20
  170. nautobot/project-static/docs/development/core/model-features.html +18 -18
  171. nautobot/project-static/docs/development/core/natural-keys.html +18 -18
  172. nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
  173. nautobot/project-static/docs/development/core/release-checklist.html +18 -18
  174. nautobot/project-static/docs/development/core/role-internals.html +18 -18
  175. nautobot/project-static/docs/development/core/settings.html +18 -18
  176. nautobot/project-static/docs/development/core/style-guide.html +19 -19
  177. nautobot/project-static/docs/development/core/templates.html +18 -18
  178. nautobot/project-static/docs/development/core/testing.html +18 -18
  179. nautobot/project-static/docs/development/core/user-preferences.html +18 -18
  180. nautobot/project-static/docs/development/index.html +18 -18
  181. nautobot/project-static/docs/development/jobs/index.html +393 -379
  182. nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
  183. nautobot/project-static/docs/index.html +9032 -13
  184. nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
  185. nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
  186. nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
  187. nautobot/project-static/docs/objects.inv +0 -0
  188. nautobot/project-static/docs/overview/application_stack.html +18 -18
  189. nautobot/project-static/docs/overview/design_philosophy.html +20 -20
  190. nautobot/project-static/docs/overview/index.html +13 -9032
  191. nautobot/project-static/docs/release-notes/index.html +252 -19
  192. nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
  193. nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
  194. nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
  195. nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
  196. nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
  197. nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
  198. nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
  199. nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
  200. nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
  201. nautobot/project-static/docs/release-notes/version-2.2.html +248 -111
  202. nautobot/project-static/docs/release-notes/version-2.3.html +644 -90
  203. nautobot/project-static/docs/requirements.txt +3 -3
  204. nautobot/project-static/docs/search/search_index.json +1 -1
  205. nautobot/project-static/docs/sitemap.xml +278 -278
  206. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  207. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
  208. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
  209. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
  210. nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
  211. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +52 -20
  212. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
  213. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
  214. nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
  215. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +22 -18
  216. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
  217. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
  218. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
  219. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
  220. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
  221. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
  222. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
  223. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
  224. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +69 -82
  225. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
  226. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -52
  227. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +80 -87
  228. nautobot/project-static/docs/user-guide/administration/installation/services.html +37 -44
  229. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
  230. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
  231. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
  232. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
  233. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
  234. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +76 -24
  235. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
  236. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
  237. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
  238. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
  239. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
  240. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
  241. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
  242. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
  243. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
  244. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
  245. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
  246. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
  247. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
  248. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
  249. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
  250. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
  251. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
  252. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
  253. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
  254. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
  255. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
  256. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
  298. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +62 -18
  299. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
  300. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
  301. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
  302. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
  303. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
  304. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
  305. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
  306. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
  307. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
  308. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
  309. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
  310. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
  311. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
  312. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
  313. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
  314. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
  315. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
  316. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
  317. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
  318. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
  319. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
  320. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
  321. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
  322. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
  323. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
  324. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
  325. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
  326. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
  327. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
  328. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
  329. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
  330. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
  331. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
  332. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
  333. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
  334. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
  335. nautobot/project-static/docs/user-guide/index.html +18 -18
  336. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
  337. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
  338. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
  339. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
  340. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
  341. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
  342. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
  343. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
  344. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
  345. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
  346. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
  347. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
  348. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
  349. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
  350. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
  351. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
  352. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
  353. nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
  354. nautobot/project-static/docs/user-guide/platform-functionality/{metadata.html → objectmetadata.html} +197 -84
  355. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
  356. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
  357. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
  358. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
  359. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
  360. nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
  361. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
  362. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
  363. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
  364. nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
  365. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
  366. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
  367. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
  368. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
  369. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
  370. nautobot/tenancy/templates/tenancy/tenant.html +4 -4
  371. nautobot/virtualization/models.py +0 -2
  372. nautobot/virtualization/tables.py +2 -5
  373. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/METADATA +3 -3
  374. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/RECORD +378 -377
  375. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  376. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  377. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/LICENSE.txt +0 -0
  378. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/NOTICE +0 -0
  379. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/WHEEL +0 -0
  380. {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/entry_points.txt +0 -0
@@ -295,6 +295,15 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
295
295
  # CELERY_TASK_SOFT_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_SOFT_TIME_LIMIT", str(5 * 60)))
296
296
  # CELERY_TASK_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_TIME_LIMIT", str(10 * 60)))
297
297
 
298
+ # How many tasks a worker is allowed to reserve for its own consumption and execution.
299
+ # If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
300
+ # For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
301
+ # Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
302
+ # so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
303
+ # until the long-running task completes.
304
+ # https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
305
+ # CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
306
+
298
307
  # Ports for prometheus metric HTTP server running on the celery worker.
299
308
  # Normally this should be set to a single port, unless you have multiple workers running on a single machine, i.e.
300
309
  # sharing the same available ports. In that case you need to specify a range of ports greater than or equal to the
@@ -307,6 +316,12 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
307
316
  # int(value) for value in os.getenv("NAUTOBOT_CELERY_WORKER_PROMETHEUS_PORTS").split(",")
308
317
  # ]
309
318
 
319
+ # If enabled stdout and stderr of running jobs will be redirected to the task logger.
320
+ # CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
321
+
322
+ # The log level of log messages generated by redirected job stdout and stderr.
323
+ # Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
324
+ # CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
310
325
 
311
326
  # Number of days to retain changelog entries. Set to 0 to retain changes indefinitely. Defaults to 90 if not set here.
312
327
  #
@@ -132,6 +132,17 @@ class FilterTestCases:
132
132
  if generic_filter_test not in self.generic_filter_tests:
133
133
  self.generic_filter_tests = (*self.generic_filter_tests, generic_filter_test)
134
134
 
135
+ # Make sure we have at least 3 contacts and 3 teams in the database
136
+ if Contact.objects.count() < 3:
137
+ Contact.objects.create(name="Generic Filter Test Contact 1")
138
+ Contact.objects.create(name="Generic Filter Test Contact 2")
139
+ Contact.objects.create(name="Generic Filter Test Contact 3")
140
+
141
+ if Team.objects.count() < 3:
142
+ Team.objects.create(name="Generic Filter Test Team 1")
143
+ Team.objects.create(name="Generic Filter Test Team 2")
144
+ Team.objects.create(name="Generic Filter Test Team 3")
145
+
135
146
  # Make sure we have some valid contact-associations:
136
147
  for contact, team, instance in zip(Contact.objects.all()[:3], Team.objects.all()[:3], self.queryset):
137
148
  ContactAssociation.objects.create(
@@ -272,7 +283,7 @@ class FilterTestCases:
272
283
  # if lookup_method is iexact use the full updated attr
273
284
  if lookup_method == "iexact":
274
285
  lookup = randomized_attr_value.upper()
275
- model_queryset = self.queryset.filter(**{f"{filter_field_name}": lookup})
286
+ model_queryset = self.queryset.filter(**{f"{filter_field_name}__iexact": lookup})
276
287
  else:
277
288
  lookup = randomized_attr_value[1:].upper()
278
289
  model_queryset = self.queryset.filter(**{f"{filter_field_name}__icontains": lookup})
@@ -24,7 +24,7 @@ class StaticMediaFailureTestCase(SeleniumTestCase):
24
24
  reverse("graphql"),
25
25
  reverse("api_docs"),
26
26
  "/admin/",
27
- "/static/docs/overview/index.html",
27
+ "/static/docs/index.html",
28
28
  ]
29
29
  for url in test_urls:
30
30
  with self.subTest(test_url=url):
@@ -3,24 +3,27 @@ from pathlib import Path
3
3
 
4
4
  from django.contrib.contenttypes.models import ContentType
5
5
  from django.core.cache import cache
6
+ from django.core.files.base import ContentFile
6
7
  from django.utils import timezone
7
8
  import yaml
8
9
 
9
10
  from nautobot.core.jobs.cleanup import CleanupTypes
10
11
  from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
11
- from nautobot.dcim.models import DeviceType, Location, LocationType, Manufacturer
12
+ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
12
13
  from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
13
14
  from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
14
15
  from nautobot.extras.models import (
15
16
  Contact,
16
17
  ContactAssociation,
17
18
  ExportTemplate,
19
+ FileProxy,
18
20
  JobLogEntry,
19
21
  JobResult,
20
22
  ObjectChange,
21
23
  Role,
22
24
  Status,
23
25
  )
26
+ from nautobot.ipam.models import Prefix
24
27
  from nautobot.users.models import ObjectPermission
25
28
 
26
29
 
@@ -218,6 +221,76 @@ class ImportObjectsTestCase(TransactionTestCase):
218
221
  )
219
222
  self.assertEqual(4, Status.objects.filter(name__startswith="test_status").count())
220
223
 
224
+ def test_csv_import_with_utf_8_with_bom_encoding(self):
225
+ """
226
+ A superuser running the job with a .csv file with utf_8 with bom encoding should successfully create all specified objects.
227
+ Test for bug fix https://github.com/nautobot/nautobot/issues/5812 and https://github.com/nautobot/nautobot/issues/5985
228
+ """
229
+
230
+ status = Status.objects.get(name="Active").pk
231
+ content = f"prefix,status\n192.168.1.1/32,{status}"
232
+ content = content.encode("utf-8-sig")
233
+ filename = "test.csv"
234
+ csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
235
+ job_result = create_job_result_and_run_job(
236
+ "nautobot.core.jobs",
237
+ "ImportObjects",
238
+ content_type=ContentType.objects.get_for_model(Prefix).pk,
239
+ csv_file=csv_file.id,
240
+ )
241
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
242
+ self.assertFalse(
243
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
244
+ )
245
+ self.assertFalse(
246
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
247
+ )
248
+ self.assertEqual(
249
+ 1, Prefix.objects.filter(status=Status.objects.get(name="Active"), prefix="192.168.1.1/32").count()
250
+ )
251
+ mfr = Manufacturer.objects.create(name="Test Cisco Manufacturer")
252
+ device_type = DeviceType.objects.create(
253
+ manufacturer=mfr,
254
+ model="Cisco CSR1000v",
255
+ u_height=0,
256
+ )
257
+ location_type = LocationType.objects.create(name="Test Location Type")
258
+ location_type.content_types.set([ContentType.objects.get_for_model(Device)])
259
+ location = Location.objects.create(
260
+ name="Device Location",
261
+ location_type=location_type,
262
+ status=Status.objects.get_for_model(Location).first(),
263
+ )
264
+ role = Role.objects.create(name="Device Status")
265
+ role.content_types.set([ContentType.objects.get_for_model(Device)])
266
+ content = "\n".join(
267
+ [
268
+ "serial,asset_tag,device_type,location,status,name,role",
269
+ f"1021C4,CA211,{device_type.pk},{location.pk},{status},Test-AC-01,{role}",
270
+ f"1021C5,CA212,{device_type.pk},{location.pk},{status},Test-AC-02,{role}",
271
+ ]
272
+ )
273
+ content = content.encode("utf-8-sig")
274
+ filename = "test.csv"
275
+ csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
276
+ job_result = create_job_result_and_run_job(
277
+ "nautobot.core.jobs",
278
+ "ImportObjects",
279
+ content_type=ContentType.objects.get_for_model(Device).pk,
280
+ csv_file=csv_file.id,
281
+ )
282
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
283
+ self.assertFalse(
284
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
285
+ )
286
+ self.assertFalse(
287
+ JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
288
+ )
289
+ device_1 = Device.objects.get(name="Test-AC-01")
290
+ device_2 = Device.objects.get(name="Test-AC-02")
291
+ self.assertEqual(device_1.serial, "1021C4")
292
+ self.assertEqual(device_2.serial, "1021C5")
293
+
221
294
  def test_csv_import_bad_row(self):
222
295
  """A row of incorrect data should fail validation for that object but import all others successfully if `roll_back_if_error` is False."""
223
296
  csv_data = self.csv_data.split("\n")
@@ -299,7 +299,7 @@ class SearchView(AccessMixin, View):
299
299
 
300
300
  # Construct the results table for this object type
301
301
  filtered_queryset = filterset({"q": form.cleaned_data["q"]}, queryset=queryset).qs
302
- table = table(filtered_queryset, orderable=False)
302
+ table = table(filtered_queryset, hide_hierarchy_ui=True, orderable=False)
303
303
  table.paginate(per_page=SEARCH_MAX_RESULTS)
304
304
 
305
305
  if table.page:
@@ -586,6 +586,7 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
586
586
 
587
587
  if form.is_valid():
588
588
  logger.debug("Form validation was successful")
589
+ msg = f"Deleted {self.queryset.model._meta.verbose_name} {obj}"
589
590
 
590
591
  try:
591
592
  obj.delete()
@@ -594,7 +595,6 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
594
595
  handle_protectederror([obj], request, e)
595
596
  return redirect(obj.get_absolute_url())
596
597
 
597
- msg = f"Deleted {self.queryset.model._meta.verbose_name} {obj}"
598
598
  logger.info(msg)
599
599
  messages.success(request, msg)
600
600
 
@@ -745,8 +745,8 @@ class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
745
745
  queryset = self.get_queryset()
746
746
  try:
747
747
  with transaction.atomic():
748
- obj.delete()
749
748
  msg = f"Deleted {queryset.model._meta.verbose_name} {obj}"
749
+ obj.delete()
750
750
  self.logger.info(msg)
751
751
  messages.success(request, msg)
752
752
  self.success_url = self.get_return_url(request, obj)
@@ -360,12 +360,14 @@ def common_detail_view_context(request, instance):
360
360
 
361
361
  if instance.is_metadata_associable_model:
362
362
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
363
- object_metadatas = instance.associated_object_metadatas.restrict(request.user, "view").order_by("scoped_fields")
364
- object_metadatas_table = ObjectMetadataTable(object_metadatas, orderable=False)
365
- object_metadatas_table.columns.hide("assigned_object")
366
- RequestConfig(request, paginate).configure(object_metadatas_table)
367
- context["associated_object_metadatas_table"] = object_metadatas_table
363
+ object_metadata = instance.associated_object_metadata.restrict(request.user, "view").order_by(
364
+ "metadata_type", "scoped_fields"
365
+ )
366
+ object_metadata_table = ObjectMetadataTable(object_metadata, orderable=False)
367
+ object_metadata_table.columns.hide("assigned_object")
368
+ RequestConfig(request, paginate).configure(object_metadata_table)
369
+ context["associated_object_metadata_table"] = object_metadata_table
368
370
  else:
369
- context["associated_object_metadatas_table"] = None
371
+ context["associated_object_metadata_table"] = None
370
372
 
371
373
  return context
nautobot/dcim/factory.py CHANGED
@@ -760,13 +760,16 @@ module_types = (
760
760
  class ModuleTypeFactory(PrimaryModelFactory):
761
761
  class Meta:
762
762
  model = ModuleType
763
- exclude = ("has_part_number",)
763
+ exclude = ("has_part_number", "has_comments")
764
764
 
765
765
  manufacturer = random_instance(Manufacturer, allow_null=False)
766
766
 
767
767
  has_part_number = NautobotBoolIterator()
768
768
  part_number = factory.Maybe("has_part_number", factory.Faker("ean", length=8), "")
769
769
 
770
+ has_comments = NautobotBoolIterator()
771
+ comments = factory.Maybe("has_comments", factory.Faker("bs"))
772
+
770
773
  @factory.lazy_attribute
771
774
  def model(self):
772
775
  """
@@ -1999,6 +1999,10 @@ class ModuleTypeFilterSet(DeviceTypeModuleTypeCommonFiltersMixin, NautobotFilter
1999
1999
  "lookup_expr": "icontains",
2000
2000
  "preprocessor": str.strip,
2001
2001
  },
2002
+ "comments": {
2003
+ "lookup_expr": "icontains",
2004
+ "preprocessor": str.strip,
2005
+ },
2002
2006
  },
2003
2007
  )
2004
2008
  has_modules = RelatedMembershipBooleanFilter(
nautobot/dcim/forms.py CHANGED
@@ -886,6 +886,7 @@ class DeviceTypeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
886
886
  software_image_files = DynamicModelMultipleChoiceField(queryset=SoftwareImageFile.objects.all(), required=False)
887
887
  u_height = forms.IntegerField(required=False)
888
888
  is_full_depth = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect(), label="Is full depth")
889
+ comments = CommentField(label="Comments", required=False)
889
890
 
890
891
  class Meta:
891
892
  nullable_fields = ["device_family", "software_image_files"]
@@ -946,6 +947,7 @@ class DeviceTypeFilterForm(NautobotFilterForm):
946
947
 
947
948
  class ModuleTypeForm(NautobotModelForm):
948
949
  manufacturer = DynamicModelChoiceField(queryset=Manufacturer.objects.all())
950
+ comments = CommentField(label="Comments")
949
951
 
950
952
  class Meta:
951
953
  model = ModuleType
@@ -953,6 +955,7 @@ class ModuleTypeForm(NautobotModelForm):
953
955
  "manufacturer",
954
956
  "model",
955
957
  "part_number",
958
+ "comments",
956
959
  "tags",
957
960
  ]
958
961
 
@@ -974,6 +977,7 @@ class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
974
977
  "manufacturer",
975
978
  "model",
976
979
  "part_number",
980
+ "comments",
977
981
  ]
978
982
 
979
983
 
@@ -981,6 +985,7 @@ class ModuleTypeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
981
985
  pk = forms.ModelMultipleChoiceField(queryset=ModuleType.objects.all(), widget=forms.MultipleHiddenInput())
982
986
  manufacturer = DynamicModelChoiceField(queryset=Manufacturer.objects.all(), required=False)
983
987
  part_number = forms.CharField(required=False)
988
+ comments = CommentField(label="Comments", required=False)
984
989
 
985
990
  class Meta:
986
991
  nullable_fields = []
@@ -2059,6 +2064,25 @@ class DeviceForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm, LocalC
2059
2064
  if position:
2060
2065
  self.fields["position"].widget.choices = [(position, f"U{position}")]
2061
2066
 
2067
+ def clean(self):
2068
+ super().clean()
2069
+
2070
+ device_type = self.cleaned_data["device_type"]
2071
+ software_image_files = self.cleaned_data["software_image_files"]
2072
+
2073
+ # If any software image file is specified, validate that
2074
+ # each of the software image files belongs to the device's device type or is a default image
2075
+ for image_file in software_image_files:
2076
+ if not image_file.default_image and device_type not in image_file.device_types.all():
2077
+ raise ValidationError(
2078
+ {
2079
+ "software_image_files": (
2080
+ f"Software image file {image_file} for version '{image_file.software_version}' is not "
2081
+ f"valid for device type {device_type}."
2082
+ )
2083
+ }
2084
+ )
2085
+
2062
2086
  def save(self, *args, **kwargs):
2063
2087
  instance = super().save(*args, **kwargs)
2064
2088
  instance.vrfs.set(self.cleaned_data["vrfs"])
@@ -138,6 +138,7 @@ class Migration(migrations.Migration):
138
138
  ),
139
139
  ("model", models.CharField(max_length=255)),
140
140
  ("part_number", models.CharField(blank=True, max_length=255)),
141
+ ("comments", models.TextField(blank=True)),
141
142
  ],
142
143
  options={
143
144
  "ordering": ("manufacturer", "model"),
@@ -26,6 +26,7 @@ from nautobot.dcim.choices import (
26
26
  PowerOutletFeedLegChoices,
27
27
  PowerOutletTypeChoices,
28
28
  PowerPortTypeChoices,
29
+ SubdeviceRoleChoices,
29
30
  )
30
31
  from nautobot.dcim.constants import (
31
32
  NONCONNECTABLE_IFACE_TYPES,
@@ -1096,6 +1097,12 @@ class DeviceBay(ComponentModel):
1096
1097
  "installed_device": f"Cannot install the specified device; device is already installed in {current_bay}"
1097
1098
  }
1098
1099
  )
1100
+ if self.installed_device.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_CHILD:
1101
+ raise ValidationError(
1102
+ {
1103
+ "installed_device": f'Cannot install device "{self.installed_device}"; device-type "{self.installed_device.device_type}" subdevice_role is not "child".'
1104
+ }
1105
+ )
1099
1106
 
1100
1107
 
1101
1108
  #
@@ -452,7 +452,6 @@ class Platform(OrganizationalModel):
452
452
  @extras_features(
453
453
  "custom_links",
454
454
  "custom_validators",
455
- "dynamic_groups",
456
455
  "export_templates",
457
456
  "graphql",
458
457
  "locations",
@@ -819,21 +818,18 @@ class Device(PrimaryModel, ConfigContextModel):
819
818
  }
820
819
  )
821
820
 
822
- # Validate device software version has a software image file that matches the device's device type or is a default image
823
- if self.software_version is not None and not any(
824
- (
825
- self.software_version.software_image_files.filter(device_types=self.device_type).exists(),
826
- self.software_version.software_image_files.filter(default_image=True).exists(),
827
- )
828
- ):
829
- raise ValidationError(
830
- {
831
- "software_version": (
832
- f"No software image files for version '{self.software_version}' are "
833
- f"valid for device type {self.device_type}."
834
- )
835
- }
836
- )
821
+ # If any software image file is specified, validate that
822
+ # each of the software image files belongs to the device's device type or is a default image
823
+ for image_file in self.software_image_files.all():
824
+ if not image_file.default_image and self.device_type not in image_file.device_types.all():
825
+ raise ValidationError(
826
+ {
827
+ "software_image_files": (
828
+ f"Software image file {image_file} for version '{image_file.software_version}' is not "
829
+ f"valid for device type {self.device_type}."
830
+ )
831
+ }
832
+ )
837
833
 
838
834
  def save(self, *args, **kwargs):
839
835
  is_new = not self.present_in_database
@@ -1109,7 +1105,6 @@ class VirtualChassis(PrimaryModel):
1109
1105
  @extras_features(
1110
1106
  "custom_links",
1111
1107
  "custom_validators",
1112
- "dynamic_groups",
1113
1108
  "export_templates",
1114
1109
  "graphql",
1115
1110
  "statuses",
@@ -1155,6 +1150,10 @@ class DeviceRedundancyGroup(PrimaryModel):
1155
1150
  def devices_sorted(self):
1156
1151
  return self.devices.order_by("device_redundancy_group_priority")
1157
1152
 
1153
+ @property
1154
+ def controllers_sorted(self):
1155
+ return self.controllers.order_by("name")
1156
+
1158
1157
  def __str__(self):
1159
1158
  return self.name
1160
1159
 
@@ -1337,7 +1336,6 @@ class SoftwareVersion(PrimaryModel):
1337
1336
  @extras_features(
1338
1337
  "custom_links",
1339
1338
  "custom_validators",
1340
- "dynamic_groups",
1341
1339
  "export_templates",
1342
1340
  "graphql",
1343
1341
  "locations",
@@ -1421,7 +1419,6 @@ class Controller(PrimaryModel):
1421
1419
  @extras_features(
1422
1420
  "custom_links",
1423
1421
  "custom_validators",
1424
- "dynamic_groups",
1425
1422
  "export_templates",
1426
1423
  "graphql",
1427
1424
  "webhooks",
@@ -1510,6 +1507,7 @@ class ModuleType(PrimaryModel):
1510
1507
  part_number = models.CharField(
1511
1508
  max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Discrete part number (optional)"
1512
1509
  )
1510
+ comments = models.TextField(blank=True)
1513
1511
 
1514
1512
  clone_fields = [
1515
1513
  "manufacturer",
@@ -1530,6 +1528,7 @@ class ModuleType(PrimaryModel):
1530
1528
  ("manufacturer", self.manufacturer.name),
1531
1529
  ("model", self.model),
1532
1530
  ("part_number", self.part_number),
1531
+ ("comments", self.comments),
1533
1532
  )
1534
1533
  )
1535
1534
 
@@ -91,7 +91,6 @@ class RackGroup(TreeModel, OrganizationalModel):
91
91
  @extras_features(
92
92
  "custom_links",
93
93
  "custom_validators",
94
- "dynamic_groups",
95
94
  "export_templates",
96
95
  "graphql",
97
96
  "locations",
@@ -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):
@@ -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">
@@ -427,7 +427,7 @@
427
427
  </div>
428
428
  {% endif %}
429
429
  {% 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 %}">
430
+ <div id="object_metadata" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadata' %}active{% else %}fade{% endif %}">
431
431
  <div class="row">
432
432
  <div class="col-md-12">
433
433
  <form method="post">
@@ -440,7 +440,7 @@
440
440
  </div>
441
441
  </div>
442
442
  <div class="table-responsive">
443
- {% render_table associated_object_metadatas_table 'inc/table.html' %}
443
+ {% render_table associated_object_metadata_table 'inc/table.html' %}
444
444
  </div>
445
445
  </div>
446
446
  </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 %}
@@ -236,7 +236,7 @@
236
236
  </div>
237
237
  {% endif %}
238
238
  {% 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 %}">
239
+ <div id="object_metadata" role="tabpanel" class="tab-pane {% if request.GET.tab == 'object_metadata' %}active{% else %}fade{% endif %}">
240
240
  <div class="row">
241
241
  <div class="col-md-12">
242
242
  <form method="post">
@@ -249,7 +249,7 @@
249
249
  </div>
250
250
  </div>
251
251
  <div class="table-responsive">
252
- {% render_table associated_object_metadatas_table 'inc/table.html' %}
252
+ {% render_table associated_object_metadata_table 'inc/table.html' %}
253
253
  </div>
254
254
  </div>
255
255
  </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,