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
@@ -1,25 +1,37 @@
1
- {% if not advanced_ui %}
2
- {% if object.has_computed_fields_basic %}
3
- {% load computed_fields %}
4
- <div class="panel panel-default">
5
- <div class="panel-heading">
6
- <strong>Computed Fields</strong>
7
- </div>
8
- <table class="table table-hover panel-body attr-table">
9
- {% computed_fields object advanced_ui %}
10
- </table>
1
+ {% load helpers %}
2
+ {% if computed_fields %}
3
+ <style>
4
+ .accordion-toggle {
5
+ font-size: 14px;
6
+ }
7
+ </style>
8
+ <div class="panel panel-default">
9
+ <div class="panel-heading">
10
+ <strong>Computed Fields</strong>
11
+ <button type="button" class="btn-xs btn-primary pull-right" id="accordion-toggle-all">Collapse All</button>
11
12
  </div>
12
- {% endif %}
13
- {% else %}
14
- {% if object.has_computed_fields_advanced %}
15
- {% load computed_fields %}
16
- <div class="panel panel-default">
17
- <div class="panel-heading">
18
- <strong>Computed Fields</strong>
19
- </div>
20
- <table class="table table-hover panel-body attr-table">
21
- {% computed_fields object advanced_ui %}
22
- </table>
23
- </div>
24
- {% endif %}
25
- {% endif %}
13
+ <table id="accordion" class="table table-hover panel-body attr-table">
14
+ {% for grouping, fields in computed_fields.items %}
15
+ {% with forloop.counter0 as count %}
16
+ {% if grouping != "" %}
17
+ <tr>
18
+ <td colspan="2"><strong>
19
+ <button type="button" class="accordion-toggle mdi mdi-chevron-down"
20
+ name="grouping.{{ grouping }}" data-toggle="collapse"
21
+ data-target=".collapseme-computed-{{ count }}">
22
+ {{ grouping }}
23
+ </button></strong>
24
+ </td>
25
+ </tr>
26
+ {% endif %}
27
+ {% for field, value in fields %}
28
+ <tr class="collapseme-computed-{{ count }} collapse in" data-parent="#accordion">
29
+ <td><span title="{{ field.description }}">{{ field }}</span></td>
30
+ <td>{{ value }}</td>
31
+ </tr>
32
+ {% endfor %}
33
+ {% endwith %}
34
+ {% endfor %}
35
+ </table>
36
+ </div>
37
+ {% endif %}
@@ -88,6 +88,6 @@
88
88
  </tbody>
89
89
  </table>
90
90
  </div>
91
- {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_advanced computed_fields_advanced_ui=True %}
91
+ {% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_advanced computed_fields=object.get_computed_fields_grouping_advanced computed_fields_advanced_ui=True %}
92
92
  {% include 'inc/relationships/panel_override.html' with relationships_fields_override=object.get_relationships_data_advanced_fields %}
93
93
  {% endif %}
@@ -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
  #
@@ -165,7 +165,7 @@ def consolidate_bulk_action_buttons(context):
165
165
 
166
166
  render_edit_button = bool(context["bulk_edit_url"] and context["permissions"]["change"])
167
167
  render_static_group_assign_button = bool(
168
- context["model"].is_dynamic_group_associable_model
168
+ getattr(context["model"], "is_dynamic_group_associable_model", False)
169
169
  and context["user"].has_perms(["extras.add_staticgroupassociation"])
170
170
  )
171
171
  render_delete_button = bool(context["bulk_delete_url"] and context["permissions"]["delete"])
@@ -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)
@@ -337,7 +337,7 @@ def common_detail_view_context(request, instance):
337
337
  context["created_by"] = created_by
338
338
  context["last_updated_by"] = last_updated_by
339
339
 
340
- if instance.is_contact_associable_model:
340
+ if getattr(instance, "is_contact_associable_model", False):
341
341
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
342
342
  associations = instance.associated_contacts.restrict(request.user, "view").order_by("role__name")
343
343
  associations_table = AssociatedContactsTable(associations, orderable=False)
@@ -347,7 +347,7 @@ def common_detail_view_context(request, instance):
347
347
  else:
348
348
  context["associated_contacts_table"] = None
349
349
 
350
- if instance.is_dynamic_group_associable_model:
350
+ if getattr(instance, "is_dynamic_group_associable_model", False):
351
351
  paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
352
352
  dynamic_groups = instance.dynamic_groups.restrict(request.user, "view")
353
353
  dynamic_groups_table = DynamicGroupTable(dynamic_groups, orderable=False)
@@ -358,14 +358,16 @@ def common_detail_view_context(request, instance):
358
358
  else:
359
359
  context["associated_dynamic_groups_table"] = None
360
360
 
361
- if instance.is_metadata_associable_model:
361
+ if getattr(instance, "is_metadata_associable_model", False):
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
@@ -110,7 +110,7 @@ NETWORK_DRIVERS = {
110
110
  "Palo Alto": ["paloalto_panos"],
111
111
  }
112
112
 
113
- TIME_ZONES = {timezone for timezone, _ in TimeZoneFormField().choices}
113
+ TIME_ZONES = sorted(timezone for timezone, _ in TimeZoneFormField().choices)
114
114
 
115
115
 
116
116
  # Retrieve correct rack reservation units
@@ -296,7 +296,7 @@ class DeviceTypeFactory(PrimaryModelFactory):
296
296
  while not unused_models:
297
297
  unused_models = {f"{device_type} {count}" for device_type in device_types}.difference(current_models)
298
298
  count += 1
299
- return factory.random.randgen.choice(list(unused_models))
299
+ return factory.random.randgen.choice(sorted(unused_models))
300
300
 
301
301
  has_part_number = NautobotBoolIterator()
302
302
  part_number = factory.Maybe("has_part_number", factory.Faker("ean", length=8), "")
@@ -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
  """
@@ -781,7 +784,7 @@ class ModuleTypeFactory(PrimaryModelFactory):
781
784
  while not unused_models:
782
785
  unused_models = {f"{module_type} {count}" for module_type in module_types}.difference(current_models)
783
786
  count += 1
784
- return factory.random.randgen.choice(list(unused_models))
787
+ return factory.random.randgen.choice(sorted(unused_models))
785
788
 
786
789
 
787
790
  class ModuleFactory(PrimaryModelFactory):
@@ -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",