nautobot 2.4.5__py3-none-any.whl → 2.4.7__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 (410) hide show
  1. nautobot/apps/forms.py +2 -0
  2. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  3. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +2 -55
  4. nautobot/circuits/views.py +20 -23
  5. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +2 -40
  6. nautobot/cloud/views.py +12 -0
  7. nautobot/core/api/mixins.py +10 -0
  8. nautobot/core/api/urls.py +2 -2
  9. nautobot/core/api/views.py +39 -1
  10. nautobot/core/celery/encoders.py +2 -2
  11. nautobot/core/forms/__init__.py +2 -0
  12. nautobot/core/forms/fields.py +23 -7
  13. nautobot/core/forms/utils.py +1 -0
  14. nautobot/core/forms/widgets.py +18 -0
  15. nautobot/core/jobs/bulk_actions.py +1 -1
  16. nautobot/core/management/commands/generate_test_data.py +1 -1
  17. nautobot/core/models/name_color_content_types.py +9 -0
  18. nautobot/core/models/validators.py +7 -0
  19. nautobot/core/settings.py +0 -14
  20. nautobot/core/settings.yaml +0 -28
  21. nautobot/core/tables.py +6 -1
  22. nautobot/core/templates/generic/object_retrieve.html +1 -1
  23. nautobot/core/templates/widgets/sluginput.html +5 -1
  24. nautobot/core/templatetags/helpers.py +15 -1
  25. nautobot/core/testing/api.py +18 -0
  26. nautobot/core/testing/integration.py +6 -2
  27. nautobot/core/tests/nautobot_config.py +0 -2
  28. nautobot/core/tests/runner.py +17 -140
  29. nautobot/core/tests/test_api.py +4 -4
  30. nautobot/core/tests/test_authentication.py +83 -4
  31. nautobot/core/tests/test_forms.py +11 -8
  32. nautobot/core/tests/test_graphql.py +9 -0
  33. nautobot/core/tests/test_jobs.py +7 -0
  34. nautobot/core/ui/object_detail.py +47 -3
  35. nautobot/core/utils/lookup.py +2 -2
  36. nautobot/dcim/factory.py +2 -0
  37. nautobot/dcim/filters/__init__.py +5 -0
  38. nautobot/dcim/forms.py +27 -1
  39. nautobot/dcim/migrations/0068_alter_softwareimagefile_download_url.py +19 -0
  40. nautobot/dcim/migrations/0069_softwareimagefile_external_integration.py +25 -0
  41. nautobot/dcim/models/devices.py +9 -2
  42. nautobot/dcim/models/locations.py +9 -0
  43. nautobot/dcim/tables/devices.py +1 -0
  44. nautobot/dcim/templates/dcim/device_list.html +1 -1
  45. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  46. nautobot/dcim/templates/dcim/manufacturer.html +1 -63
  47. nautobot/dcim/templates/dcim/module_list.html +1 -1
  48. nautobot/dcim/templates/dcim/moduletype_retrieve.html +1 -1
  49. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +4 -0
  50. nautobot/dcim/tests/integration/test_module_bay_position.py +125 -0
  51. nautobot/dcim/tests/test_api.py +74 -31
  52. nautobot/dcim/tests/test_filters.py +2 -0
  53. nautobot/dcim/tests/test_models.py +78 -0
  54. nautobot/dcim/tests/test_views.py +7 -1
  55. nautobot/dcim/urls.py +1 -45
  56. nautobot/dcim/views.py +35 -66
  57. nautobot/extras/choices.py +4 -0
  58. nautobot/extras/filters/__init__.py +6 -0
  59. nautobot/extras/forms/forms.py +83 -11
  60. nautobot/extras/models/customfields.py +10 -9
  61. nautobot/extras/plugins/marketplace_manifest.yml +18 -0
  62. nautobot/extras/signals.py +43 -4
  63. nautobot/extras/tables.py +4 -5
  64. nautobot/extras/tasks.py +4 -2
  65. nautobot/extras/templates/extras/contact_retrieve.html +1 -58
  66. nautobot/extras/templates/extras/exporttemplate.html +1 -53
  67. nautobot/extras/templates/extras/inc/panel_changelog.html +1 -1
  68. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  69. nautobot/extras/templates/extras/status.html +1 -37
  70. nautobot/extras/templates/extras/team_retrieve.html +1 -58
  71. nautobot/extras/tests/integration/test_notes.py +1 -1
  72. nautobot/extras/tests/test_api.py +22 -7
  73. nautobot/extras/tests/test_changelog.py +4 -4
  74. nautobot/extras/tests/test_customfields.py +27 -0
  75. nautobot/extras/tests/test_plugins.py +19 -13
  76. nautobot/extras/tests/test_relationships.py +9 -0
  77. nautobot/extras/tests/test_tags.py +2 -2
  78. nautobot/extras/tests/test_views.py +37 -6
  79. nautobot/extras/urls.py +3 -100
  80. nautobot/extras/views.py +111 -133
  81. nautobot/ipam/tables.py +7 -2
  82. nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -41
  83. nautobot/ipam/templates/ipam/service.html +2 -46
  84. nautobot/ipam/templates/ipam/service_edit.html +1 -17
  85. nautobot/ipam/templates/ipam/service_retrieve.html +7 -0
  86. nautobot/ipam/tests/migration/__init__.py +0 -0
  87. nautobot/ipam/tests/migration/test_migrations.py +510 -0
  88. nautobot/ipam/tests/test_api.py +66 -36
  89. nautobot/ipam/tests/test_filters.py +0 -10
  90. nautobot/ipam/tests/test_views.py +44 -2
  91. nautobot/ipam/urls.py +2 -47
  92. nautobot/ipam/utils/migrations.py +185 -152
  93. nautobot/ipam/utils/testing.py +177 -0
  94. nautobot/ipam/views.py +95 -157
  95. nautobot/project-static/css/base.css +5 -0
  96. nautobot/project-static/docs/404.html +0 -2
  97. nautobot/project-static/docs/apps/index.html +0 -2
  98. nautobot/project-static/docs/apps/nautobot-apps.html +0 -2
  99. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +0 -2
  100. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +0 -2
  101. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +0 -2
  102. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +0 -2
  103. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +0 -2
  104. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +0 -2
  105. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +0 -2
  106. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +0 -2
  107. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +0 -2
  108. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +0 -2
  109. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +0 -2
  110. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +0 -2
  111. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +62 -2
  112. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +0 -2
  113. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +0 -2
  114. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +47 -2
  115. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +0 -2
  116. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +0 -2
  117. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -2
  118. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +0 -2
  119. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +70 -5
  120. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +0 -2
  121. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +0 -2
  122. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +0 -2
  123. nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -2
  124. nautobot/project-static/docs/development/apps/api/database-backend-config.html +0 -2
  125. nautobot/project-static/docs/development/apps/api/models/django-admin.html +0 -2
  126. nautobot/project-static/docs/development/apps/api/models/global-search.html +0 -2
  127. nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -2
  128. nautobot/project-static/docs/development/apps/api/models/index.html +0 -2
  129. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +0 -2
  130. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +0 -2
  131. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -2
  132. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +0 -2
  133. nautobot/project-static/docs/development/apps/api/platform-features/index.html +0 -2
  134. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -2
  135. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +0 -2
  136. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -2
  137. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -2
  138. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +0 -2
  139. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +0 -2
  140. nautobot/project-static/docs/development/apps/api/prometheus.html +0 -2
  141. nautobot/project-static/docs/development/apps/api/setup.html +0 -2
  142. nautobot/project-static/docs/development/apps/api/testing.html +0 -89
  143. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -2
  144. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -2
  145. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +0 -2
  146. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +0 -2
  147. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -2
  148. nautobot/project-static/docs/development/apps/api/views/base-template.html +0 -2
  149. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -2
  150. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +0 -2
  151. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +0 -2
  152. nautobot/project-static/docs/development/apps/api/views/index.html +0 -2
  153. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +0 -2
  154. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -2
  155. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -2
  156. nautobot/project-static/docs/development/apps/api/views/notes.html +0 -2
  157. nautobot/project-static/docs/development/apps/api/views/rest-api.html +0 -2
  158. nautobot/project-static/docs/development/apps/api/views/urls.html +0 -2
  159. nautobot/project-static/docs/development/apps/index.html +0 -2
  160. nautobot/project-static/docs/development/apps/migration/code-updates.html +0 -2
  161. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -3
  162. nautobot/project-static/docs/development/apps/migration/from-v1.html +0 -2
  163. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +0 -2
  164. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +0 -2
  165. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +0 -2
  166. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +0 -2
  167. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +0 -2
  168. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +0 -2
  169. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +0 -2
  170. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +0 -2
  171. nautobot/project-static/docs/development/apps/porting-from-netbox.html +0 -2
  172. nautobot/project-static/docs/development/core/application-registry.html +0 -2
  173. nautobot/project-static/docs/development/core/best-practices.html +3 -5
  174. nautobot/project-static/docs/development/core/bootstrap-ui.html +0 -2
  175. nautobot/project-static/docs/development/core/caching.html +0 -2
  176. nautobot/project-static/docs/development/core/controllers.html +0 -2
  177. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +0 -2
  178. nautobot/project-static/docs/development/core/generic-views.html +0 -2
  179. nautobot/project-static/docs/development/core/getting-started.html +78 -109
  180. nautobot/project-static/docs/development/core/homepage.html +0 -2
  181. nautobot/project-static/docs/development/core/index.html +0 -2
  182. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +0 -2
  183. nautobot/project-static/docs/development/core/model-checklist.html +0 -2
  184. nautobot/project-static/docs/development/core/model-features.html +0 -2
  185. nautobot/project-static/docs/development/core/natural-keys.html +0 -2
  186. nautobot/project-static/docs/development/core/navigation-menu.html +0 -2
  187. nautobot/project-static/docs/development/core/release-checklist.html +1 -3
  188. nautobot/project-static/docs/development/core/role-internals.html +0 -2
  189. nautobot/project-static/docs/development/core/settings.html +0 -2
  190. nautobot/project-static/docs/development/core/style-guide.html +1 -3
  191. nautobot/project-static/docs/development/core/templates.html +0 -2
  192. nautobot/project-static/docs/development/core/testing.html +24 -200
  193. nautobot/project-static/docs/development/core/ui-component-framework.html +0 -2
  194. nautobot/project-static/docs/development/core/user-preferences.html +0 -2
  195. nautobot/project-static/docs/development/index.html +0 -2
  196. nautobot/project-static/docs/development/jobs/index.html +0 -2
  197. nautobot/project-static/docs/development/jobs/migration/from-v1.html +0 -2
  198. nautobot/project-static/docs/index.html +0 -2
  199. nautobot/project-static/docs/media/user-guide/administration/getting-started/nautobot-cloud.png +0 -0
  200. nautobot/project-static/docs/objects.inv +0 -0
  201. nautobot/project-static/docs/overview/application_stack.html +1 -3
  202. nautobot/project-static/docs/overview/design_philosophy.html +0 -2
  203. nautobot/project-static/docs/release-notes/index.html +0 -2
  204. nautobot/project-static/docs/release-notes/version-1.0.html +0 -2
  205. nautobot/project-static/docs/release-notes/version-1.1.html +0 -2
  206. nautobot/project-static/docs/release-notes/version-1.2.html +0 -2
  207. nautobot/project-static/docs/release-notes/version-1.3.html +0 -2
  208. nautobot/project-static/docs/release-notes/version-1.4.html +0 -2
  209. nautobot/project-static/docs/release-notes/version-1.5.html +0 -2
  210. nautobot/project-static/docs/release-notes/version-1.6.html +0 -2
  211. nautobot/project-static/docs/release-notes/version-2.0.html +0 -2
  212. nautobot/project-static/docs/release-notes/version-2.1.html +0 -2
  213. nautobot/project-static/docs/release-notes/version-2.2.html +0 -2
  214. nautobot/project-static/docs/release-notes/version-2.3.html +0 -2
  215. nautobot/project-static/docs/release-notes/version-2.4.html +364 -3
  216. nautobot/project-static/docs/search/search_index.json +1 -1
  217. nautobot/project-static/docs/sitemap.xml +290 -290
  218. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  219. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +0 -2
  220. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +0 -2
  221. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +0 -2
  222. nautobot/project-static/docs/user-guide/administration/configuration/index.html +0 -2
  223. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +0 -2
  224. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +2 -50
  225. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +0 -2
  226. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +0 -2
  227. nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -2
  228. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +0 -2
  229. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +71 -2
  230. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +0 -2
  231. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +0 -2
  232. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +0 -2
  233. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +0 -2
  234. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +0 -2
  235. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +0 -2
  236. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +0 -2
  237. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -3
  238. nautobot/project-static/docs/user-guide/administration/installation/index.html +257 -18
  239. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +0 -2
  240. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +0 -2
  241. nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -2
  242. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +0 -2
  243. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +0 -2
  244. nautobot/project-static/docs/user-guide/administration/security/index.html +0 -2
  245. nautobot/project-static/docs/user-guide/administration/security/notices.html +0 -2
  246. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -3
  247. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +0 -2
  248. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +0 -2
  249. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +0 -2
  250. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +0 -2
  251. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +0 -2
  252. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +0 -2
  253. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +0 -2
  254. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +0 -2
  255. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +0 -2
  256. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -4
  257. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +0 -2
  258. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +0 -2
  259. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +0 -2
  260. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +0 -2
  261. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -2
  262. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +0 -2
  263. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +0 -2
  264. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +0 -2
  265. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +0 -2
  266. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +0 -2
  267. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +0 -2
  268. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +0 -2
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +0 -2
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -2
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -2
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -2
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -2
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +0 -2
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +0 -2
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +0 -2
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -2
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -2
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +0 -2
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -2
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -2
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -2
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -2
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +0 -2
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -2
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -2
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -2
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -2
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +0 -2
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +0 -2
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +0 -2
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +0 -2
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +0 -2
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +0 -2
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -2
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +0 -2
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -2
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -2
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +0 -2
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -2
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -2
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +0 -2
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +0 -2
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +0 -2
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -2
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -2
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -2
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +0 -2
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +0 -2
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +0 -2
  311. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -2
  312. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -2
  313. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +0 -2
  314. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +0 -2
  315. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -2
  316. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +0 -2
  317. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +0 -2
  318. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +0 -2
  319. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +0 -2
  320. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +0 -2
  321. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -2
  322. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +0 -2
  323. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +0 -2
  324. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +0 -2
  325. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +0 -2
  326. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +0 -2
  327. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +0 -2
  328. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +0 -2
  329. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +0 -2
  330. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +0 -2
  331. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -2
  332. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +0 -2
  333. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +0 -2
  334. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +0 -2
  335. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +0 -2
  336. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -13
  337. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +0 -2
  338. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +8 -10
  339. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -2
  340. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +0 -2
  341. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +40 -27
  342. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -6
  343. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -3
  344. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +77 -7
  345. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -3
  346. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -3
  347. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -3
  348. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -2
  349. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +0 -2
  350. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +0 -2
  351. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +0 -2
  352. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +0 -2
  353. nautobot/project-static/docs/user-guide/index.html +89 -4
  354. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +0 -2
  355. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -2
  356. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +0 -2
  357. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +0 -2
  358. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -2
  359. nautobot/project-static/docs/user-guide/platform-functionality/events.html +0 -2
  360. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +0 -2
  361. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +0 -2
  362. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -2
  363. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -2
  364. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +0 -2
  365. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +0 -2
  366. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -2
  367. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -2
  368. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -2
  369. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -2
  370. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +0 -2
  371. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +0 -2
  372. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -2
  373. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +0 -2
  374. nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -2
  375. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +0 -2
  376. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +0 -2
  377. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +0 -2
  378. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -2
  379. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -2
  380. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -2
  381. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +0 -2
  382. nautobot/project-static/docs/user-guide/platform-functionality/role.html +0 -2
  383. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +0 -2
  384. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +0 -2
  385. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +0 -2
  386. nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -2
  387. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -2
  388. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +0 -2
  389. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +0 -2
  390. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +0 -2
  391. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +207 -124
  392. nautobot/project-static/js/forms.js +88 -37
  393. nautobot/project-static/js/homepage_layout.js +12 -3
  394. nautobot/virtualization/forms.py +20 -0
  395. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -39
  396. nautobot/virtualization/templates/virtualization/clustertype.html +1 -0
  397. nautobot/virtualization/tests/test_api.py +14 -3
  398. nautobot/virtualization/tests/test_views.py +10 -2
  399. nautobot/virtualization/urls.py +10 -93
  400. nautobot/virtualization/views.py +33 -72
  401. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/METADATA +6 -5
  402. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/RECORD +407 -402
  403. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/WHEEL +1 -1
  404. nautobot/core/tests/performance_baselines.yml +0 -8900
  405. nautobot/dcim/templates/dcim/modulebay_create.html +0 -39
  406. nautobot/ipam/tests/test_migrations.py +0 -462
  407. /nautobot/ipam/templates/ipam/{namespace_ipaddresses.html → namespace_ip_addresses.html} +0 -0
  408. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/LICENSE.txt +0 -0
  409. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/NOTICE +0 -0
  410. {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/entry_points.txt +0 -0
nautobot/dcim/views.py CHANGED
@@ -28,7 +28,6 @@ from rest_framework.exceptions import MethodNotAllowed
28
28
  from rest_framework.response import Response
29
29
 
30
30
  from nautobot.circuits.models import Circuit
31
- from nautobot.cloud.models import CloudAccount
32
31
  from nautobot.cloud.tables import CloudAccountTable
33
32
  from nautobot.core.choices import ButtonColorChoices
34
33
  from nautobot.core.exceptions import AbortTransaction
@@ -304,7 +303,7 @@ class LocationView(generic.ObjectView):
304
303
  .select_related("parent", "location_type")
305
304
  )
306
305
 
307
- children_table = tables.LocationTable(children)
306
+ children_table = tables.LocationTable(children, hide_hierarchy_ui=True)
308
307
 
309
308
  paginate = {
310
309
  "paginator_class": EnhancedPaginator,
@@ -724,71 +723,40 @@ class RackReservationBulkDeleteView(generic.BulkDeleteView):
724
723
  #
725
724
 
726
725
 
727
- class ManufacturerListView(generic.ObjectListView):
726
+ class ManufacturerUIViewSet(NautobotUIViewSet):
727
+ bulk_update_form_class = forms.ManufacturerBulkEditForm
728
+ filterset_class = filters.ManufacturerFilterSet
729
+ filterset_form_class = forms.ManufacturerFilterForm
730
+ form_class = forms.ManufacturerForm
731
+ serializer_class = serializers.ManufacturerSerializer
732
+ table_class = tables.ManufacturerTable
728
733
  queryset = Manufacturer.objects.all()
729
- filterset = filters.ManufacturerFilterSet
730
- filterset_form = forms.ManufacturerFilterForm
731
- table = tables.ManufacturerTable
732
734
 
733
-
734
- class ManufacturerView(generic.ObjectView):
735
- queryset = Manufacturer.objects.all()
736
-
737
- def get_extra_context(self, request, instance):
738
- # Devices
739
- devices = (
740
- Device.objects.restrict(request.user, "view")
741
- .filter(device_type__manufacturer=instance)
742
- .select_related("status", "location", "tenant", "role", "rack", "device_type")
743
- )
744
-
745
- device_table = tables.DeviceTable(devices)
746
-
747
- paginate = {
748
- "paginator_class": EnhancedPaginator,
749
- "per_page": get_paginate_count(request),
750
- }
751
- RequestConfig(request, paginate).configure(device_table)
752
-
753
- # Cloud Accounts
754
- cloud_accounts = (
755
- CloudAccount.objects.restrict(request.user, "view")
756
- .filter(provider=instance)
757
- .select_related("secrets_group")
758
- )
759
-
760
- cloud_account_table = CloudAccountTable(cloud_accounts)
761
- paginate = {
762
- "paginator_class": EnhancedPaginator,
763
- "per_page": get_paginate_count(request),
764
- }
765
- RequestConfig(request, paginate).configure(cloud_account_table)
766
-
767
- return {
768
- "device_table": device_table,
769
- "cloud_account_table": cloud_account_table,
770
- **super().get_extra_context(request, instance),
771
- }
772
-
773
-
774
- class ManufacturerEditView(generic.ObjectEditView):
775
- queryset = Manufacturer.objects.all()
776
- model_form = forms.ManufacturerForm
777
-
778
-
779
- class ManufacturerDeleteView(generic.ObjectDeleteView):
780
- queryset = Manufacturer.objects.all()
781
-
782
-
783
- class ManufacturerBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
784
- queryset = Manufacturer.objects.all()
785
- table = tables.ManufacturerTable
786
-
787
-
788
- class ManufacturerBulkDeleteView(generic.BulkDeleteView):
789
- queryset = Manufacturer.objects.all()
790
- table = tables.ManufacturerTable
791
- filterset = filters.ManufacturerFilterSet
735
+ # Object detail content with devices and cloud accounts related to the manufacturer
736
+ object_detail_content = object_detail.ObjectDetailContent(
737
+ panels=(
738
+ object_detail.ObjectFieldsPanel(
739
+ weight=100,
740
+ section=SectionChoices.LEFT_HALF,
741
+ fields="__all__",
742
+ ),
743
+ object_detail.ObjectsTablePanel(
744
+ weight=100,
745
+ section=SectionChoices.FULL_WIDTH,
746
+ table_class=tables.DeviceTable,
747
+ table_filter="device_type__manufacturer",
748
+ related_field_name="manufacturer",
749
+ exclude_columns=["manufacturer"],
750
+ ),
751
+ object_detail.ObjectsTablePanel(
752
+ weight=100,
753
+ section=SectionChoices.FULL_WIDTH,
754
+ table_class=CloudAccountTable,
755
+ table_filter="provider",
756
+ exclude_columns=["provider"],
757
+ ),
758
+ ),
759
+ )
792
760
 
793
761
 
794
762
  #
@@ -1769,6 +1737,7 @@ class DeviceView(generic.ObjectView):
1769
1737
  weight=100,
1770
1738
  color=ButtonColorChoices.BLUE,
1771
1739
  label="Add Components",
1740
+ attributes={"id": "device-add-components-button"},
1772
1741
  icon="mdi-plus-thick",
1773
1742
  required_permissions=["dcim.change_device"],
1774
1743
  children=(
@@ -3436,7 +3405,7 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
3436
3405
  model_form_class = forms.ModuleBayForm
3437
3406
  serializer_class = serializers.ModuleBaySerializer
3438
3407
  table_class = tables.ModuleBayTable
3439
- create_template_name = "dcim/modulebay_create.html"
3408
+ create_template_name = "dcim/device_component_add.html"
3440
3409
 
3441
3410
  def get_extra_context(self, request, instance):
3442
3411
  if instance:
@@ -488,6 +488,8 @@ class SecretsGroupSecretTypeChoices(ChoiceSet):
488
488
  TYPE_SECRET = "secret" # noqa: S105 # hardcoded-password-string -- false positive
489
489
  TYPE_TOKEN = "token" # noqa: S105 # hardcoded-password-string -- false positive
490
490
  TYPE_USERNAME = "username"
491
+ TYPE_URL = "url"
492
+ TYPE_NOTES = "notes"
491
493
 
492
494
  CHOICES = (
493
495
  (TYPE_KEY, "Key"),
@@ -495,6 +497,8 @@ class SecretsGroupSecretTypeChoices(ChoiceSet):
495
497
  (TYPE_SECRET, "Secret"),
496
498
  (TYPE_TOKEN, "Token"),
497
499
  (TYPE_USERNAME, "Username"),
500
+ (TYPE_URL, "URL"),
501
+ (TYPE_NOTES, "Notes"),
498
502
  )
499
503
 
500
504
 
@@ -526,6 +526,12 @@ class ContactTeamFilterSet(NameSearchFilterSet, NautobotFilterSet):
526
526
 
527
527
 
528
528
  class ContactFilterSet(ContactTeamFilterSet):
529
+ teams = NaturalKeyOrPKMultipleChoiceFilter(
530
+ queryset=Team.objects.all(),
531
+ to_field_name="name",
532
+ label="Team (name or ID)",
533
+ )
534
+
529
535
  class Meta:
530
536
  model = Contact
531
537
  fields = "__all__"
@@ -142,6 +142,7 @@ __all__ = (
142
142
  "DynamicGroupFilterForm",
143
143
  "DynamicGroupForm",
144
144
  "DynamicGroupMembershipFormSet",
145
+ "ExportTemplateBulkEditForm",
145
146
  "ExportTemplateFilterForm",
146
147
  "ExportTemplateForm",
147
148
  "ExternalIntegrationBulkEditForm",
@@ -180,6 +181,7 @@ __all__ = (
180
181
  "ObjectMetadataFilterForm",
181
182
  "PasswordInputWithPlaceholder",
182
183
  "RelationshipAssociationFilterForm",
184
+ "RelationshipBulkEditForm",
183
185
  "RelationshipFilterForm",
184
186
  "RelationshipForm",
185
187
  "RoleBulkEditForm",
@@ -519,10 +521,11 @@ class CustomFieldBulkDeleteForm(ConfirmationForm):
519
521
  else:
520
522
  context = change_context.as_dict(queryset)
521
523
  context["context_detail"] = "bulk delete custom field data"
522
- tasks = [
523
- delete_custom_field_data.si(obj.key, set(obj.content_types.values_list("pk", flat=True)), context)
524
- for obj in queryset
525
- ]
524
+ tasks = []
525
+ for obj in queryset:
526
+ pk_set = set(obj.content_types.values_list("pk", flat=True))
527
+ if pk_set:
528
+ tasks.append(delete_custom_field_data.si(obj.key, pk_set, context))
526
529
  return tasks
527
530
 
528
531
  def perform_pre_delete(self, queryset):
@@ -533,10 +536,11 @@ class CustomFieldBulkDeleteForm(ConfirmationForm):
533
536
  logger.error("Celery worker process not running. Object custom fields may fail to reflect this deletion.")
534
537
  return
535
538
  tasks = self.construct_custom_field_delete_tasks(queryset)
536
- # Executing the tasks in the background sequentially using chain() aligns with how a single
537
- # CustomField object is deleted. We decided to not check the result because it needs at least one worker
538
- # to be active and comes with extra performance penalty.
539
- chain(*tasks).apply_async()
539
+ if tasks:
540
+ # Executing the tasks in the background sequentially using chain() aligns with how a single
541
+ # CustomField object is deleted. We decided to not check the result because it needs at least one worker
542
+ # to be active and comes with extra performance penalty.
543
+ chain(*tasks).apply_async()
540
544
 
541
545
 
542
546
  #
@@ -749,6 +753,31 @@ class StaticGroupAssociationFilterForm(NautobotFilterForm):
749
753
  #
750
754
  # Export Templates
751
755
  #
756
+ class ExportTemplateBulkEditForm(NautobotBulkEditForm):
757
+ pk = forms.ModelMultipleChoiceField(queryset=ExportTemplate.objects.all(), widget=forms.MultipleHiddenInput())
758
+
759
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
760
+ mime_type = forms.CharField(
761
+ max_length=CHARFIELD_MAX_LENGTH,
762
+ required=False,
763
+ label="MIME type",
764
+ help_text="Defaults to <code>text/plain</code>",
765
+ )
766
+ file_extension = forms.CharField(
767
+ max_length=CHARFIELD_MAX_LENGTH, required=False, help_text="Extension to append to the rendered filename"
768
+ )
769
+
770
+ content_type = forms.ModelChoiceField(
771
+ queryset=ContentType.objects.filter(FeatureQuery("export_templates").get_query()).order_by(
772
+ "app_label", "model"
773
+ ),
774
+ required=False,
775
+ label="Content Type",
776
+ )
777
+
778
+ class Meta:
779
+ model = ExportTemplate
780
+ nullable_fields = ["description", "mime_type", "file_extension"]
752
781
 
753
782
 
754
783
  class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
@@ -1774,6 +1803,45 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
1774
1803
  #
1775
1804
 
1776
1805
 
1806
+ class RelationshipBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditFormMixin, NoteModelBulkEditFormMixin):
1807
+ pk = forms.ModelMultipleChoiceField(queryset=Relationship.objects.all(), widget=forms.MultipleHiddenInput())
1808
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1809
+ type = forms.ChoiceField(
1810
+ required=False,
1811
+ label="type",
1812
+ choices=add_blank_choice(RelationshipTypeChoices),
1813
+ )
1814
+ source_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
1815
+ destination_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
1816
+ source_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the source")
1817
+ destination_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the destination")
1818
+ source_type = CSVContentTypeField(
1819
+ queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()), required=False
1820
+ )
1821
+ destination_type = CSVContentTypeField(
1822
+ queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()), required=False
1823
+ )
1824
+ source_label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1825
+ destination_label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1826
+ advanced_ui = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
1827
+
1828
+ class Meta:
1829
+ model = Relationship
1830
+ fields = [
1831
+ "description",
1832
+ "type",
1833
+ "source_hidden",
1834
+ "destination_hidden",
1835
+ "source_filter",
1836
+ "destination_filter",
1837
+ "source_type",
1838
+ "destination_type",
1839
+ "source_label",
1840
+ "destination_label",
1841
+ "advanced_ui",
1842
+ ]
1843
+
1844
+
1777
1845
  class RelationshipForm(BootstrapMixin, forms.ModelForm):
1778
1846
  key = SlugField(
1779
1847
  help_text="Internal name of this relationship. Please use underscores rather than dashes.",
@@ -1891,8 +1959,11 @@ class RoleBulkEditForm(NautobotBulkEditForm):
1891
1959
  color = forms.CharField(max_length=6, required=False, widget=ColorSelect())
1892
1960
  description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1893
1961
  weight = forms.IntegerField(required=False)
1894
- content_types = MultipleContentTypeField(
1895
- queryset=RoleModelsQuery().as_queryset(), required=False, label="Content Type(s)"
1962
+ add_content_types = MultipleContentTypeField(
1963
+ queryset=RoleModelsQuery().as_queryset(), required=False, label="Add Content Type(s)"
1964
+ )
1965
+ remove_content_types = MultipleContentTypeField(
1966
+ queryset=RoleModelsQuery().as_queryset(), required=False, label="Remove Content Type(s)"
1896
1967
  )
1897
1968
 
1898
1969
  class Meta:
@@ -2012,7 +2083,8 @@ class StatusBulkEditForm(NautobotBulkEditForm):
2012
2083
 
2013
2084
  pk = forms.ModelMultipleChoiceField(queryset=Status.objects.all(), widget=forms.MultipleHiddenInput)
2014
2085
  color = forms.CharField(max_length=6, required=False, widget=ColorSelect())
2015
- content_types = MultipleContentTypeField(feature="statuses", required=False, label="Content Type(s)")
2086
+ add_content_types = MultipleContentTypeField(feature="statuses", required=False, label="Add Content Type(s)")
2087
+ remove_content_types = MultipleContentTypeField(feature="statuses", required=False, label="Remove Content Type(s)")
2016
2088
 
2017
2089
  class Meta:
2018
2090
  nullable_fields = []
@@ -822,16 +822,17 @@ class CustomField(
822
822
 
823
823
  super().delete(*args, **kwargs)
824
824
 
825
- # Circular Import
826
- from nautobot.extras.signals import change_context_state
825
+ if content_types:
826
+ # Circular Import
827
+ from nautobot.extras.signals import change_context_state
827
828
 
828
- change_context = change_context_state.get()
829
- if change_context is None:
830
- context = None
831
- else:
832
- context = change_context.as_dict(instance=self)
833
- context["context_detail"] = "delete custom field data"
834
- delete_custom_field_data.delay(self.key, content_types, context)
829
+ change_context = change_context_state.get()
830
+ if change_context is None:
831
+ context = None
832
+ else:
833
+ context = change_context.as_dict(instance=self)
834
+ context["context_detail"] = "delete custom field data"
835
+ delete_custom_field_data.delay(self.key, content_types, context)
835
836
 
836
837
  def add_prefix_to_cf_key(self):
837
838
  return "cf_" + str(self.key)
@@ -94,6 +94,24 @@
94
94
  "availability": "Open Source"
95
95
  "icon": >-
96
96
  data:image/png;base64,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
97
+ - "name": "DNS Models"
98
+ "use_cases":
99
+ - "DNS"
100
+ "requires": []
101
+ "docs": "https://docs.nautobot.com/projects/dns-models/en/latest/"
102
+ "headline": "Model DNS Zones and their associated records in a vendor-neutral manner."
103
+ "description": >-
104
+ DNS Models allows users to document and understand the network by modeling their DNS data. DNS Records can then
105
+ be directly related to data about the network.
106
+ "package_name": "nautobot_dns_models"
107
+ "author": "Network to Code (NTC)"
108
+ "display":
109
+ "cloud": true
110
+ "oss": true
111
+ "enterprise": true
112
+ "availability": "Open Source"
113
+ "icon": >-
114
+ data:image/png;base64,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
97
115
  - "name": "Data Validation Engine"
98
116
  "use_cases":
99
117
  - "Data Management"
@@ -125,6 +125,17 @@ def invalidate_relationship_models_cache(sender, **kwargs):
125
125
  cache.delete_pattern(f"{method.cache_key_prefix}.*")
126
126
 
127
127
 
128
+ @receiver(post_save, sender=CustomField)
129
+ @receiver(post_delete, sender=CustomField)
130
+ @receiver(post_save, sender=Relationship)
131
+ @receiver(m2m_changed, sender=Relationship)
132
+ @receiver(post_delete, sender=Relationship)
133
+ def invalidate_openapi_schema_cache(sender, **kwargs):
134
+ """Invalidate the openapi schema cache."""
135
+ with contextlib.suppress(redis.exceptions.ConnectionError):
136
+ cache.delete_pattern("openapi_schema_cache_*")
137
+
138
+
128
139
  @receiver(post_save)
129
140
  @receiver(m2m_changed)
130
141
  def _handle_changed_object(sender, instance, raw=False, **kwargs):
@@ -327,7 +338,9 @@ def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs)
327
338
 
328
339
  def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
329
340
  """
330
- Handle the cleanup of old custom field data when a CustomField is removed from one or more ContentTypes.
341
+ Handle provisioning/deprovisioning of custom_field_data when there are changes to CustomField.content_types.
342
+
343
+ The name of this function is misleading as this signal applies to *added* content-types as well.
331
344
  """
332
345
 
333
346
  change_context = change_context_state.get()
@@ -335,13 +348,39 @@ def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
335
348
  context = None
336
349
  else:
337
350
  context = change_context.as_dict(instance=instance)
338
- if action == "post_remove":
339
- # Existing content types have been removed from the custom field, delete their data
351
+
352
+ if action == "pre_remove":
353
+ # Existing content types may be removed from the custom field, delete their data if so.
354
+ # CAUTION: pk_set in this _remove case is the content-types that were *requested* to remove,
355
+ # **not** the content-types that actually *will need to be* removed. In other words, this is not idempotent:
356
+ # my_cf.content_types.remove(device_ct) --> pk_set = {device_ct.pk}
357
+ # my_cf.content_types.remove(device_ct) --> pk_set = {device_ct.pk} again even though it was already gone
358
+ # So we need to check which content types will actually be removed to not create unnecessary tasks:
359
+ removed_pk_set = pk_set.intersection(instance.content_types.values_list("pk", flat=True))
360
+ if not removed_pk_set:
361
+ return
362
+
363
+ if context:
364
+ context["context_detail"] = "delete custom field data from existing content types"
365
+ transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, removed_pk_set, context))
366
+
367
+ elif action == "pre_clear":
368
+ # In this case, the provided pk_set is always empty, so we need to look at the current values instead:
369
+ cleared_pk_set = set(instance.content_types.values_list("pk", flat=True))
370
+ if not cleared_pk_set:
371
+ return
372
+
340
373
  if context:
341
374
  context["context_detail"] = "delete custom field data from existing content types"
342
- transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, pk_set, context))
375
+ transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, cleared_pk_set, context))
343
376
 
344
377
  elif action == "post_add":
378
+ # Unlike the above _remove case, in the _add case pk_set is the *new* content-types only,
379
+ # and for whatever reason, Django triggers this signal even if there was no actual change.
380
+ # To avoid creating unnecessary background tasks, we need to check for this case ourselves:
381
+ if not pk_set:
382
+ return
383
+
345
384
  # New content types have been added to the custom field, provision them
346
385
  if context:
347
386
  context["context_detail"] = "provision custom field data for new content types"
nautobot/extras/tables.py CHANGED
@@ -1063,14 +1063,13 @@ class ObjectMetadataTable(BaseTable):
1063
1063
 
1064
1064
  class NoteTable(BaseTable):
1065
1065
  actions = ButtonsColumn(Note)
1066
- created = tables.LinkColumn()
1067
- note = tables.Column(
1068
- attrs={"td": {"class": "rendered-markdown"}},
1069
- )
1066
+ created = tables.DateTimeColumn(linkify=True)
1067
+ last_updated = tables.DateTimeColumn()
1068
+ note = tables.Column()
1070
1069
 
1071
1070
  class Meta(BaseTable.Meta):
1072
1071
  model = Note
1073
- fields = ("created", "note", "user_name")
1072
+ fields = ("created", "last_updated", "note", "user_name")
1074
1073
 
1075
1074
  def render_note(self, value):
1076
1075
  return render_markdown(value)
nautobot/extras/tasks.py CHANGED
@@ -121,12 +121,13 @@ def delete_custom_field_data(field_key, content_type_pk_set, change_context=None
121
121
  for ct in ContentType.objects.filter(pk__in=content_type_pk_set):
122
122
  model = ct.model_class()
123
123
  queryset = model.objects.filter(**{f"_custom_field_data__{field_key}__isnull": False})
124
+ pk_list = []
124
125
  if change_context is not None:
125
126
  pk_list = list(queryset.values_list("pk", flat=True))
126
127
  task_logger.info("Deleting existing values for custom field `%s` from %s records...", field_key, ct.model)
127
128
  count = queryset.update(_custom_field_data=JSONRemove("_custom_field_data", field_key))
128
129
  task_logger.info("Updated %d records", count)
129
- if change_context is not None:
130
+ if count and change_context is not None:
130
131
  # Since we used update() above, we bypassed ObjectChange automatic creation via signals. Create them now
131
132
  _generate_bulk_object_changes(change_context, model.objects.filter(pk__in=pk_list), task_logger)
132
133
 
@@ -155,6 +156,7 @@ def provision_field(field_id, content_type_pk_set, change_context=None):
155
156
  for ct in ContentType.objects.filter(pk__in=content_type_pk_set):
156
157
  model = ct.model_class()
157
158
  queryset = model.objects.filter(**{f"_custom_field_data__{field.key}__isnull": True})
159
+ pk_list = []
158
160
  if change_context is not None:
159
161
  pk_list = list(queryset.values_list("pk", flat=True))
160
162
  task_logger.info(
@@ -165,7 +167,7 @@ def provision_field(field_id, content_type_pk_set, change_context=None):
165
167
  )
166
168
  count = queryset.update(_custom_field_data=JSONSet("_custom_field_data", field.key, field.default))
167
169
  task_logger.info("Updated %d records.", count)
168
- if change_context is not None:
170
+ if count and change_context is not None:
169
171
  # Since we used update() above, we bypassed ObjectChange automatic creation via signals. Create them now
170
172
  _generate_bulk_object_changes(change_context, model.objects.filter(pk__in=pk_list), task_logger)
171
173
 
@@ -1,59 +1,2 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
- {% load helpers %}
3
-
4
- {% block content_left_page %}
5
- <div class="panel panel-default">
6
- <div class="panel-heading">
7
- <strong>Contact</strong>
8
- </div>
9
- <table class="table table-hover panel-body attr-table">
10
- <tr>
11
- <td>Name</td>
12
- <td>{{ object.name }}</td>
13
- </tr>
14
- <tr>
15
- <td>Phone</td>
16
- <td>{{ object.phone|hyperlinked_phone_number }}</td>
17
- </tr>
18
- <tr>
19
- <td>E-mail</td>
20
- <td>{{ object.email|hyperlinked_email }}</td>
21
- </tr>
22
- <tr>
23
- <td>Address</td>
24
- <td>
25
- {% if object.address %}
26
- <div class="pull-right noprint">
27
- <a href="https://maps.google.com/?q={{ object.address|urlencode }}" target="_blank" class="btn btn-primary btn-xs">
28
- <i class="mdi mdi-map-marker"></i> Map it
29
- </a>
30
- </div>
31
- <span>{{ object.address|linebreaksbr }}</span>
32
- {% else %}
33
- <span class="text-muted">&mdash;</span>
34
- {% endif %}
35
- </td>
36
- </tr>
37
- </table>
38
- </div>
39
- <div class="panel panel-default">
40
- <div class="panel-heading">
41
- <strong>Comments</strong>
42
- </div>
43
- <div class="panel-body rendered-markdown">
44
- {% if object.comments %}
45
- {{ object.comments|render_markdown }}
46
- {% else %}
47
- <span class="text-muted">None</span>
48
- {% endif %}
49
- </div>
50
- </div>
51
- {% endblock %}
52
-
53
- {% block content_right_page %}
54
- {% include 'panel_table.html' with table=teams_table heading='Assigned Teams' %}
55
- {% endblock %}
56
-
57
- {% block content_full_width_page %}
58
- {% include 'panel_table.html' with table=contact_associations_table heading='Contact For' %}
59
- {% endblock %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -1,54 +1,2 @@
1
1
  {% extends 'generic/object_retrieve.html' %}
2
- {% load helpers %}
3
-
4
- {% block content_left_page %}
5
- <div class="panel panel-default">
6
- <div class="panel-heading">
7
- <strong>Details</strong>
8
- </div>
9
-
10
- <table class="table table-hover panel-body attr-table">
11
- <tr>
12
- <td>Name</td>
13
- <td><span>{{ object.name }}</span></td>
14
- </tr>
15
- <tr>
16
- <td>Owner</td>
17
- <td>{{ object.owner|hyperlinked_object }}</td>
18
- </tr>
19
- <tr>
20
- <td>Description</td>
21
- <td>{{ object.description|placeholder }}</td>
22
- </tr>
23
- </table>
24
- </div>
25
-
26
- <div class="panel panel-default">
27
- <div class="panel-heading">
28
- <strong>Template</strong>
29
- </div>
30
- <table class="table table-hover panel-body attr-table">
31
- <tr>
32
- <td>Object Type</td>
33
- <td><span>{{ object.content_type }}</span></td>
34
- </tr>
35
- <tr>
36
- <td>Mime Type</td>
37
- <td><span>{{ object.mime_type }}</span></td>
38
- </tr>
39
- <tr>
40
- <td>File Extension</td>
41
- <td><span>{{ object.file_extension }}</span></td>
42
- </tr>
43
- </table>
44
- </div>
45
- {% endblock content_left_page %}
46
-
47
- {% block content_right_page %}
48
- <div class="panel panel-default">
49
- <div class="panel-heading">
50
- <strong>Code Template</strong>
51
- </div>
52
- {% if object.template_code %} <pre>{{ object.template_code }}</pre> {% else %} {{ None }} {% endif %}
53
- </div>
54
- {% endblock content_right_page %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -21,7 +21,7 @@
21
21
  <br />
22
22
  <small>
23
23
  <span class="text-muted">{{ change.user_name }} -</span>
24
- <a href="{{ change.get_absolute_url }}" class="text-muted">{{ change.time|date:'SHORT_DATETIME_FORMAT' }}</a>
24
+ <a href="{{ change.get_absolute_url }}" class="text-muted">{{ change.time|date:settings.SHORT_DATETIME_FORMAT }}</a>
25
25
  </small>
26
26
  </div>
27
27
  {% endwith %}
@@ -7,7 +7,7 @@
7
7
  <span class="pull-right" title="{{ result.date_created }}">{% include 'extras/inc/job_label.html' %}</span>
8
8
  <br>
9
9
  <small>
10
- <span class="text-muted">{{ result.user }} - {{ result.date_done|date:'SHORT_DATETIME_FORMAT' }}</span>
10
+ <span class="text-muted">{{ result.user }} - {{ result.date_done|date:settings.SHORT_DATETIME_FORMAT }}</span>
11
11
  </small>
12
12
  </div>
13
13
  {% if forloop.last %}