nautobot 2.4.13__py3-none-any.whl → 2.4.14__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.
Files changed (356) hide show
  1. nautobot/core/graphql/generators.py +8 -0
  2. nautobot/core/graphql/schema.py +30 -30
  3. nautobot/core/management/commands/migrate.py +90 -1
  4. nautobot/core/settings.yaml +3 -3
  5. nautobot/core/tables.py +4 -4
  6. nautobot/core/testing/api.py +7 -0
  7. nautobot/core/views/utils.py +1 -1
  8. nautobot/dcim/choices.py +2 -0
  9. nautobot/dcim/constants.py +0 -16
  10. nautobot/dcim/factory.py +1 -1
  11. nautobot/dcim/templates/dcim/rack.html +2 -318
  12. nautobot/dcim/templates/dcim/rack_edit.html +2 -47
  13. nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
  14. nautobot/dcim/templates/dcim/rack_update.html +47 -0
  15. nautobot/dcim/urls.py +2 -27
  16. nautobot/dcim/utils.py +4 -30
  17. nautobot/dcim/views.py +38 -69
  18. nautobot/extras/choices.py +12 -4
  19. nautobot/extras/filters/mixins.py +8 -6
  20. nautobot/extras/forms/forms.py +9 -0
  21. nautobot/extras/forms/mixins.py +4 -2
  22. nautobot/extras/migrations/0062_collect_roles_from_related_apps_roles.py +30 -7
  23. nautobot/extras/migrations/0124_add_joblogentry_index.py +16 -0
  24. nautobot/extras/models/customfields.py +52 -3
  25. nautobot/extras/models/jobs.py +6 -0
  26. nautobot/extras/models/relationships.py +55 -6
  27. nautobot/extras/templates/extras/graphqlquery.html +2 -97
  28. nautobot/extras/templates/extras/graphqlquery_list.html +1 -0
  29. nautobot/extras/templates/extras/graphqlquery_retrieve.html +97 -0
  30. nautobot/extras/templates/extras/secretsgroup.html +2 -29
  31. nautobot/extras/templates/extras/secretsgroup_edit.html +2 -82
  32. nautobot/extras/templates/extras/secretsgroup_retrieve.html +29 -0
  33. nautobot/extras/templates/extras/secretsgroup_update.html +82 -0
  34. nautobot/extras/tests/test_customfields.py +115 -7
  35. nautobot/extras/tests/test_relationships.py +7 -1
  36. nautobot/extras/tests/test_views.py +113 -1
  37. nautobot/extras/urls.py +2 -51
  38. nautobot/extras/utils.py +4 -1
  39. nautobot/extras/views.py +39 -133
  40. nautobot/ipam/api/views.py +69 -6
  41. nautobot/ipam/tests/test_api.py +350 -0
  42. nautobot/project-static/docs/404.html +11 -34
  43. nautobot/project-static/docs/apps/index.html +11 -34
  44. nautobot/project-static/docs/apps/nautobot-apps.html +11 -34
  45. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js → bundle.50899def.min.js} +2 -2
  46. nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js.map → bundle.50899def.min.js.map} +2 -2
  47. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css → main.7e37652d.min.css} +1 -1
  48. nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css.map → main.7e37652d.min.css.map} +1 -1
  49. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +11 -34
  50. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +11 -34
  51. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +11 -34
  52. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +11 -34
  53. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +11 -34
  54. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +11 -34
  55. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +11 -34
  56. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +11 -34
  57. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +11 -34
  58. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +11 -34
  59. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +11 -34
  60. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +11 -34
  61. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +11 -34
  62. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +11 -34
  63. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +11 -34
  64. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -34
  65. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +11 -34
  66. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +11 -34
  67. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -34
  68. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11 -34
  69. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +11 -34
  70. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +11 -34
  71. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +11 -34
  72. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +11 -34
  73. nautobot/project-static/docs/development/apps/api/configuration-view.html +11 -34
  74. nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -34
  75. nautobot/project-static/docs/development/apps/api/models/django-admin.html +11 -34
  76. nautobot/project-static/docs/development/apps/api/models/global-search.html +11 -34
  77. nautobot/project-static/docs/development/apps/api/models/graphql.html +11 -34
  78. nautobot/project-static/docs/development/apps/api/models/index.html +11 -34
  79. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +11 -34
  80. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +11 -34
  81. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +11 -34
  82. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +11 -34
  83. nautobot/project-static/docs/development/apps/api/platform-features/index.html +11 -34
  84. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -34
  85. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +11 -34
  86. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +11 -34
  87. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +11 -34
  88. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +11 -34
  89. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +11 -34
  90. nautobot/project-static/docs/development/apps/api/prometheus.html +11 -34
  91. nautobot/project-static/docs/development/apps/api/setup.html +11 -34
  92. nautobot/project-static/docs/development/apps/api/testing.html +11 -34
  93. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +11 -34
  94. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +11 -34
  95. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +11 -34
  96. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +11 -34
  97. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +11 -34
  98. nautobot/project-static/docs/development/apps/api/views/base-template.html +11 -34
  99. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +11 -34
  100. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +11 -34
  101. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +11 -34
  102. nautobot/project-static/docs/development/apps/api/views/index.html +11 -34
  103. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +11 -34
  104. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +11 -34
  105. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +11 -34
  106. nautobot/project-static/docs/development/apps/api/views/notes.html +11 -34
  107. nautobot/project-static/docs/development/apps/api/views/rest-api.html +11 -34
  108. nautobot/project-static/docs/development/apps/api/views/urls.html +11 -34
  109. nautobot/project-static/docs/development/apps/index.html +11 -34
  110. nautobot/project-static/docs/development/apps/migration/code-updates.html +11 -34
  111. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +11 -34
  112. nautobot/project-static/docs/development/apps/migration/from-v1.html +11 -34
  113. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +11 -34
  114. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +11 -34
  115. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +11 -34
  116. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +11 -34
  117. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +11 -34
  118. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +11 -34
  119. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +11 -34
  120. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +11 -34
  121. nautobot/project-static/docs/development/apps/porting-from-netbox.html +11 -34
  122. nautobot/project-static/docs/development/core/application-registry.html +139 -133
  123. nautobot/project-static/docs/development/core/best-practices.html +11 -34
  124. nautobot/project-static/docs/development/core/bootstrap-ui.html +11 -34
  125. nautobot/project-static/docs/development/core/caching.html +11 -34
  126. nautobot/project-static/docs/development/core/controllers.html +11 -34
  127. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +11 -34
  128. nautobot/project-static/docs/development/core/generic-views.html +11 -34
  129. nautobot/project-static/docs/development/core/getting-started.html +11 -34
  130. nautobot/project-static/docs/development/core/homepage.html +11 -34
  131. nautobot/project-static/docs/development/core/index.html +11 -34
  132. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +11 -34
  133. nautobot/project-static/docs/development/core/model-checklist.html +11 -34
  134. nautobot/project-static/docs/development/core/model-features.html +11 -34
  135. nautobot/project-static/docs/development/core/natural-keys.html +11 -34
  136. nautobot/project-static/docs/development/core/navigation-menu.html +11 -34
  137. nautobot/project-static/docs/development/core/release-checklist.html +11 -34
  138. nautobot/project-static/docs/development/core/role-internals.html +11 -34
  139. nautobot/project-static/docs/development/core/settings.html +11 -34
  140. nautobot/project-static/docs/development/core/style-guide.html +11 -34
  141. nautobot/project-static/docs/development/core/templates.html +11 -34
  142. nautobot/project-static/docs/development/core/testing.html +11 -34
  143. nautobot/project-static/docs/development/core/ui-component-framework.html +11 -34
  144. nautobot/project-static/docs/development/core/user-preferences.html +11 -34
  145. nautobot/project-static/docs/development/index.html +11 -34
  146. nautobot/project-static/docs/development/jobs/getting-started.html +11 -34
  147. nautobot/project-static/docs/development/jobs/index.html +11 -34
  148. nautobot/project-static/docs/development/jobs/installation.html +11 -34
  149. nautobot/project-static/docs/development/jobs/job-extensions.html +11 -34
  150. nautobot/project-static/docs/development/jobs/job-logging.html +11 -34
  151. nautobot/project-static/docs/development/jobs/job-patterns.html +11 -34
  152. nautobot/project-static/docs/development/jobs/job-structure.html +11 -34
  153. nautobot/project-static/docs/development/jobs/migration/from-v1.html +11 -34
  154. nautobot/project-static/docs/development/jobs/testing.html +11 -34
  155. nautobot/project-static/docs/index.html +11 -34
  156. nautobot/project-static/docs/overview/application_stack.html +11 -34
  157. nautobot/project-static/docs/overview/design_philosophy.html +11 -34
  158. nautobot/project-static/docs/release-notes/index.html +11 -34
  159. nautobot/project-static/docs/release-notes/version-1.0.html +11 -34
  160. nautobot/project-static/docs/release-notes/version-1.1.html +11 -34
  161. nautobot/project-static/docs/release-notes/version-1.2.html +11 -34
  162. nautobot/project-static/docs/release-notes/version-1.3.html +11 -34
  163. nautobot/project-static/docs/release-notes/version-1.4.html +11 -34
  164. nautobot/project-static/docs/release-notes/version-1.5.html +11 -34
  165. nautobot/project-static/docs/release-notes/version-1.6.html +11 -34
  166. nautobot/project-static/docs/release-notes/version-2.0.html +11 -34
  167. nautobot/project-static/docs/release-notes/version-2.1.html +11 -34
  168. nautobot/project-static/docs/release-notes/version-2.2.html +11 -34
  169. nautobot/project-static/docs/release-notes/version-2.3.html +11 -34
  170. nautobot/project-static/docs/release-notes/version-2.4.html +171 -34
  171. nautobot/project-static/docs/requirements.txt +1 -1
  172. nautobot/project-static/docs/search/search_index.json +1 -1
  173. nautobot/project-static/docs/sitemap.xml +299 -299
  174. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  175. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +11 -34
  176. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +11 -34
  177. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +11 -34
  178. nautobot/project-static/docs/user-guide/administration/configuration/index.html +11 -34
  179. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +11 -34
  180. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +14 -37
  181. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +11 -34
  182. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +11 -34
  183. nautobot/project-static/docs/user-guide/administration/guides/docker.html +11 -34
  184. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +11 -34
  185. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +11 -34
  186. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +11 -34
  187. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +11 -34
  188. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +11 -34
  189. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +11 -34
  190. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +11 -34
  191. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +11 -34
  192. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +11 -34
  193. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +11 -34
  194. nautobot/project-static/docs/user-guide/administration/installation/index.html +11 -34
  195. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +11 -34
  196. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +11 -34
  197. nautobot/project-static/docs/user-guide/administration/installation/services.html +11 -34
  198. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +11 -34
  199. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +11 -34
  200. nautobot/project-static/docs/user-guide/administration/security/index.html +11 -34
  201. nautobot/project-static/docs/user-guide/administration/security/notices.html +11 -34
  202. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +19 -39
  203. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +11 -34
  204. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +11 -34
  205. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +11 -34
  206. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +11 -34
  207. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +11 -34
  208. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +11 -34
  209. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +11 -34
  210. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +11 -34
  211. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +11 -34
  212. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +11 -34
  213. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +11 -34
  214. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +11 -34
  215. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +11 -34
  216. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +11 -34
  217. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +11 -34
  218. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +11 -34
  219. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +11 -34
  220. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +11 -34
  221. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +11 -34
  222. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +11 -34
  223. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +11 -34
  224. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +11 -34
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +11 -34
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +11 -34
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +11 -34
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +11 -34
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +11 -34
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +11 -34
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +11 -34
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +11 -34
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +11 -34
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +11 -34
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +11 -34
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +11 -34
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +11 -34
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +11 -34
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +11 -34
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +14 -37
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +11 -34
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +11 -34
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +19 -52
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +11 -34
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +11 -34
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +11 -34
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +11 -34
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +14 -37
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +11 -34
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +11 -34
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +11 -34
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +11 -34
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +11 -34
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +11 -34
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +11 -34
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +11 -34
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +11 -34
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +11 -34
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +11 -34
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +11 -34
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +11 -34
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +11 -34
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +11 -34
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +11 -34
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +11 -34
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +11 -34
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +11 -34
  268. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +11 -34
  269. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +11 -34
  270. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -34
  271. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -34
  272. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +11 -34
  273. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +11 -34
  274. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +11 -34
  275. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +11 -34
  276. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +11 -34
  277. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +11 -34
  278. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +11 -34
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +11 -34
  280. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +11 -34
  281. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +11 -34
  282. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +11 -34
  283. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +11 -34
  284. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +11 -34
  285. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +11 -34
  286. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +11 -34
  287. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +11 -34
  288. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +11 -34
  289. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +11 -34
  290. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +11 -34
  291. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +11 -34
  292. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +11 -34
  293. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -34
  294. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +11 -34
  295. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +11 -34
  296. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +11 -34
  297. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +11 -34
  298. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +11 -34
  299. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +11 -34
  300. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +11 -34
  301. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +11 -34
  302. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +11 -34
  303. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +11 -34
  304. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +11 -34
  305. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +11 -34
  306. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +11 -34
  307. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +11 -34
  308. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +11 -34
  309. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +11 -34
  310. nautobot/project-static/docs/user-guide/index.html +11 -34
  311. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +11 -34
  312. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +11 -34
  313. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +11 -34
  314. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +11 -34
  315. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +11 -34
  316. nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -34
  317. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +11 -34
  318. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +11 -34
  319. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +11 -34
  320. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +11 -34
  321. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +11 -34
  322. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +11 -34
  323. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +11 -34
  324. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +11 -34
  325. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +11 -34
  326. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +11 -34
  327. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +11 -34
  328. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +11 -34
  329. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +11 -34
  330. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +11 -34
  331. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +11 -34
  332. nautobot/project-static/docs/user-guide/platform-functionality/note.html +11 -34
  333. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +11 -34
  334. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +11 -34
  335. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +11 -34
  336. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +11 -34
  337. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +11 -34
  338. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +11 -34
  339. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +11 -34
  340. nautobot/project-static/docs/user-guide/platform-functionality/role.html +11 -34
  341. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +11 -34
  342. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -34
  343. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +11 -34
  344. nautobot/project-static/docs/user-guide/platform-functionality/status.html +11 -34
  345. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +11 -34
  346. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +11 -34
  347. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +11 -34
  348. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +11 -34
  349. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +11 -34
  350. nautobot/tenancy/api/views.py +2 -1
  351. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/METADATA +5 -5
  352. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/RECORD +356 -349
  353. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/LICENSE.txt +0 -0
  354. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/NOTICE +0 -0
  355. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/WHEEL +0 -0
  356. {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/entry_points.txt +0 -0
@@ -1,82 +1,2 @@
1
- {% extends 'generic/object_create.html' %}
2
- {% load static %}
3
- {% load form_helpers %}
4
-
5
- {% block form %}
6
- <div class="panel panel-default">
7
- <div class="panel-heading"><strong>Secrets Group</strong></div>
8
- <div class="panel-body">
9
- {% render_field form.name %}
10
- {% render_field form.description %}
11
- </div>
12
- </div>
13
- <div class="panel panel-default">
14
- <div class="panel-heading"><strong>Secret Assignment</strong></div>
15
- <div class="panel-body">
16
- {% if secrets.errors %}
17
- <div class="text-danger">
18
- Please correct the error(s) below:
19
-
20
- {% for secret in secrets.forms %}
21
- {% if secret.errors %}
22
- {% for error in secret.errors.values %}{{ error }}{% endfor %}
23
- {% endif %}
24
- {% endfor %}
25
- </div>
26
- {% endif %}
27
- {{ secrets.non_field_errors }}
28
- <table class="table" id="secrets">
29
- {{ secrets.management_form }}
30
- {% for secret_form in secrets.forms %}
31
- {% if forloop.first %}
32
- <thead>
33
- <tr>
34
- {% for field in secret_form.visible_fields %}
35
- <th>{{ field.label|capfirst }}</th>
36
- {% endfor %}
37
- </tr>
38
- </thead>
39
- {% endif %}
40
- <tr class="formset_row-{{ secrets.prefix }}">
41
- {% for field in secret_form.visible_fields %}
42
- <td>
43
- {% if forloop.first %}
44
- {% for hidden in secret_form.hidden_fields %}
45
- {{ hidden }}
46
- {% endfor %}
47
- {% endif %}
48
- {{ field }}
49
- {% if field.errors %}
50
- <ul>
51
- {% for error in field.errors %}
52
- {# Embed an HTML comment indicating the error for extraction by tests #}
53
- <!-- FORM-ERROR {{ field.name }}: {{ error }} -->
54
- <li class="text-danger">{{ error }}</li>
55
- {% endfor %}
56
- </ul>
57
- {% endif %}
58
- </td>
59
- {% endfor %}
60
- </tr>
61
- {% endfor %}
62
- </table>
63
- </div>
64
- </div>
65
- {% include "inc/extras_features_edit_form_fields.html" %}
66
- {% endblock form %}
67
-
68
- {% block javascript %}
69
- {{ block.super }}
70
- <script src="{% static 'jquery/jquery.formset.js' %}"></script>
71
- <script type="text/javascript">
72
- $('.formset_row-{{ secrets.prefix }}').formset({
73
- addText: '<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add another Secret',
74
- addCssClass: 'btn btn-primary add-row',
75
- deleteText: '<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span>',
76
- deleteCssClass: 'btn btn-danger delete-row',
77
- prefix: '{{ secrets.prefix }}',
78
- formCssClass: 'dynamic-formset-{{ secrets.prefix }}',
79
- added: jsify_form
80
- });
81
- </script>
82
- {% endblock javascript %}
1
+ {% extends 'extras/secretsgroup_update.html' %}
2
+ {% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
@@ -0,0 +1,29 @@
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>Secrets Group</strong>
8
+ </div>
9
+ <table class="table table-hover panel-body attr-table">
10
+ <tr>
11
+ <td>Description</td>
12
+ <td colspan="3">{{ object.description | placeholder }}</td>
13
+ </tr>
14
+ <tr>
15
+ <td rowspan="{{ secrets_group_associations | length | add:'1'}}">Secrets</td>
16
+ <th>Access Type</th>
17
+ <th>Secret Type</th>
18
+ <th>Secret</th>
19
+ </tr>
20
+ {% for secret_association in secrets_group_associations %}
21
+ <tr>
22
+ <td>{{ secret_association.access_type }}</td>
23
+ <td>{{ secret_association.secret_type | bettertitle }}</td>
24
+ <td>{{ secret_association.secret|hyperlinked_object }}</td>
25
+ </tr>
26
+ {% endfor %}
27
+ </table>
28
+ </div>
29
+ {% endblock content_left_page %}
@@ -0,0 +1,82 @@
1
+ {% extends 'generic/object_create.html' %}
2
+ {% load static %}
3
+ {% load form_helpers %}
4
+
5
+ {% block form %}
6
+ <div class="panel panel-default">
7
+ <div class="panel-heading"><strong>Secrets Group</strong></div>
8
+ <div class="panel-body">
9
+ {% render_field form.name %}
10
+ {% render_field form.description %}
11
+ </div>
12
+ </div>
13
+ <div class="panel panel-default">
14
+ <div class="panel-heading"><strong>Secret Assignment</strong></div>
15
+ <div class="panel-body">
16
+ {% if secrets.errors %}
17
+ <div class="text-danger">
18
+ Please correct the error(s) below:
19
+
20
+ {% for secret in secrets.forms %}
21
+ {% if secret.errors %}
22
+ {% for error in secret.errors.values %}{{ error }}{% endfor %}
23
+ {% endif %}
24
+ {% endfor %}
25
+ </div>
26
+ {% endif %}
27
+ {{ secrets.non_field_errors }}
28
+ <table class="table" id="secrets">
29
+ {{ secrets.management_form }}
30
+ {% for secret_form in secrets.forms %}
31
+ {% if forloop.first %}
32
+ <thead>
33
+ <tr>
34
+ {% for field in secret_form.visible_fields %}
35
+ <th>{{ field.label|capfirst }}</th>
36
+ {% endfor %}
37
+ </tr>
38
+ </thead>
39
+ {% endif %}
40
+ <tr class="formset_row-{{ secrets.prefix }}">
41
+ {% for field in secret_form.visible_fields %}
42
+ <td>
43
+ {% if forloop.first %}
44
+ {% for hidden in secret_form.hidden_fields %}
45
+ {{ hidden }}
46
+ {% endfor %}
47
+ {% endif %}
48
+ {{ field }}
49
+ {% if field.errors %}
50
+ <ul>
51
+ {% for error in field.errors %}
52
+ {# Embed an HTML comment indicating the error for extraction by tests #}
53
+ <!-- FORM-ERROR {{ field.name }}: {{ error }} -->
54
+ <li class="text-danger">{{ error }}</li>
55
+ {% endfor %}
56
+ </ul>
57
+ {% endif %}
58
+ </td>
59
+ {% endfor %}
60
+ </tr>
61
+ {% endfor %}
62
+ </table>
63
+ </div>
64
+ </div>
65
+ {% include "inc/extras_features_edit_form_fields.html" %}
66
+ {% endblock form %}
67
+
68
+ {% block javascript %}
69
+ {{ block.super }}
70
+ <script src="{% static 'jquery/jquery.formset.js' %}"></script>
71
+ <script type="text/javascript">
72
+ $('.formset_row-{{ secrets.prefix }}').formset({
73
+ addText: '<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add another Secret',
74
+ addCssClass: 'btn btn-primary add-row',
75
+ deleteText: '<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span>',
76
+ deleteCssClass: 'btn btn-danger delete-row',
77
+ prefix: '{{ secrets.prefix }}',
78
+ formCssClass: 'dynamic-formset-{{ secrets.prefix }}',
79
+ added: jsify_form
80
+ });
81
+ </script>
82
+ {% endblock javascript %}
@@ -4,7 +4,7 @@ import logging
4
4
  from django.conf import settings
5
5
  from django.contrib.contenttypes.models import ContentType
6
6
  from django.core.exceptions import ValidationError
7
- from django.db.models import ProtectedError
7
+ from django.db.models import ProtectedError, QuerySet
8
8
  from django.forms import ChoiceField, IntegerField, NumberInput
9
9
  from django.urls import reverse
10
10
  from rest_framework import status
@@ -400,28 +400,46 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
400
400
  class CustomFieldManagerTest(TestCase):
401
401
  def setUp(self):
402
402
  self.content_type = ContentType.objects.get_for_model(Location)
403
- custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Text Field", default="foo")
403
+ custom_field = CustomField(
404
+ type=CustomFieldTypeChoices.TYPE_TEXT,
405
+ label="Text Field",
406
+ default="foo",
407
+ filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
408
+ )
404
409
  custom_field.save()
405
410
  custom_field.content_types.set([self.content_type])
406
411
 
407
412
  def test_get_for_model(self):
408
413
  self.assertEqual(CustomField.objects.get_for_model(Location).count(), 2)
409
414
  self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0)
415
+ self.assertEqual(len(CustomField.objects.get_for_model(Location, get_queryset=False)), 2)
416
+ self.assertEqual(len(CustomField.objects.get_for_model(VirtualMachine, get_queryset=False)), 0)
410
417
 
411
418
  def test_get_for_model_caching_and_cache_invalidation(self):
412
419
  """Test that the cache is used and is properly invalidated when CustomFields are created or deleted."""
413
420
  # Assert that the cache is used when calling get_for_model a second time
414
421
  CustomField.objects.get_for_model(Location)
415
422
  with self.assertNumQueries(0):
416
- CustomField.objects.get_for_model(Location)
423
+ qs = CustomField.objects.get_for_model(Location)
424
+ with self.assertNumQueries(0):
425
+ listing = CustomField.objects.get_for_model(Location, get_queryset=False)
426
+ self.assertIsInstance(qs, QuerySet)
427
+ self.assertIsInstance(listing, list)
428
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
417
429
 
418
430
  # Assert that different values of exclude_filter_disabled are cached separately
419
431
  with self.assertNumQueries(1):
420
432
  CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
421
433
  with self.assertNumQueries(0):
422
- CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
434
+ qs = CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
435
+ with self.assertNumQueries(0):
436
+ listing = CustomField.objects.get_for_model(Location, exclude_filter_disabled=True, get_queryset=False)
423
437
  with self.assertNumQueries(0):
424
438
  CustomField.objects.get_for_model(Location)
439
+ self.assertIsInstance(qs, QuerySet)
440
+ self.assertIsInstance(listing, list)
441
+ self.assertEqual(1, len(listing))
442
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
425
443
 
426
444
  # Assert that different models are cached separately
427
445
  with self.assertNumQueries(1):
@@ -444,21 +462,111 @@ class CustomFieldManagerTest(TestCase):
444
462
  with self.assertNumQueries(1):
445
463
  CustomField.objects.get_for_model(Location)
446
464
  with self.assertNumQueries(0):
447
- CustomField.objects.get_for_model(Location)
465
+ qs = CustomField.objects.get_for_model(Location)
466
+ with self.assertNumQueries(0):
467
+ listing = CustomField.objects.get_for_model(Location, get_queryset=False)
468
+ self.assertIsInstance(qs, QuerySet)
469
+ self.assertIsInstance(listing, list)
470
+ self.assertEqual(3, len(listing))
471
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
448
472
 
449
473
  # Assert that the cache is invalidated when removing a CustomField.content_types m2m relationship
450
474
  custom_field.content_types.set([])
451
475
  with self.assertNumQueries(1):
452
476
  CustomField.objects.get_for_model(Location)
453
477
  with self.assertNumQueries(0):
454
- CustomField.objects.get_for_model(Location)
478
+ qs = CustomField.objects.get_for_model(Location)
479
+ with self.assertNumQueries(0):
480
+ listing = CustomField.objects.get_for_model(Location, get_queryset=False)
481
+ self.assertIsInstance(qs, QuerySet)
482
+ self.assertIsInstance(listing, list)
483
+ self.assertEqual(2, len(listing))
484
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
455
485
 
456
486
  # Assert that the cache is invalidated on object delete
457
487
  custom_field.delete()
458
488
  with self.assertNumQueries(1):
459
489
  CustomField.objects.get_for_model(Location)
460
490
  with self.assertNumQueries(0):
461
- CustomField.objects.get_for_model(Location)
491
+ qs = CustomField.objects.get_for_model(Location)
492
+ with self.assertNumQueries(0):
493
+ listing = CustomField.objects.get_for_model(Location, get_queryset=False)
494
+ self.assertIsInstance(qs, QuerySet)
495
+ self.assertIsInstance(listing, list)
496
+ self.assertEqual(2, len(listing))
497
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
498
+
499
+
500
+ class ComputedFieldManagerTestCase(TestCase):
501
+ def setUp(self):
502
+ self.content_type = ContentType.objects.get_for_model(Location)
503
+ ComputedField.objects.create(
504
+ content_type=ContentType.objects.get_for_model(Location),
505
+ key="computed_field_one",
506
+ label="Computed Field One",
507
+ template="{{ obj.name }} is the name of this location.",
508
+ fallback_value="An error occurred while rendering this template.",
509
+ weight=100,
510
+ )
511
+
512
+ def test_get_for_model(self):
513
+ self.assertEqual(ComputedField.objects.get_for_model(Location).count(), 1)
514
+ self.assertEqual(ComputedField.objects.get_for_model(VirtualMachine).count(), 0)
515
+ self.assertEqual(len(ComputedField.objects.get_for_model(Location, get_queryset=False)), 1)
516
+ self.assertEqual(len(ComputedField.objects.get_for_model(VirtualMachine, get_queryset=False)), 0)
517
+
518
+ def test_get_for_model_caching_and_cache_invalidation(self):
519
+ # Assert that the cache is used when calling get_for_model a second time
520
+ ComputedField.objects.get_for_model(Location)
521
+ with self.assertNumQueries(0):
522
+ qs = ComputedField.objects.get_for_model(Location)
523
+ with self.assertNumQueries(0):
524
+ listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
525
+ self.assertIsInstance(qs, QuerySet)
526
+ self.assertIsInstance(listing, list)
527
+ self.assertEqual(1, len(listing))
528
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
529
+
530
+ # Assert that different models are cached separately
531
+ with self.assertNumQueries(1):
532
+ ComputedField.objects.get_for_model(VirtualMachine)
533
+ with self.assertNumQueries(0):
534
+ ComputedField.objects.get_for_model(VirtualMachine)
535
+ with self.assertNumQueries(0):
536
+ ComputedField.objects.get_for_model(Location)
537
+
538
+ # Assert that the cache is invalidated on object save
539
+ computed_field = ComputedField.objects.create(
540
+ content_type=ContentType.objects.get_for_model(Location),
541
+ key="computed_field_two",
542
+ label="Computed Field Two",
543
+ template="{{ obj.name }} is still jthe name of this location.",
544
+ fallback_value="An error occurred while rendering this template.",
545
+ weight=200,
546
+ )
547
+ with self.assertNumQueries(1):
548
+ ComputedField.objects.get_for_model(Location)
549
+ with self.assertNumQueries(0):
550
+ qs = ComputedField.objects.get_for_model(Location)
551
+ with self.assertNumQueries(0):
552
+ listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
553
+ self.assertIsInstance(qs, QuerySet)
554
+ self.assertIsInstance(listing, list)
555
+ self.assertEqual(2, len(listing))
556
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
557
+
558
+ # Assert that the cache is invalided on object delete
559
+ computed_field.delete()
560
+ with self.assertNumQueries(1):
561
+ ComputedField.objects.get_for_model(Location)
562
+ with self.assertNumQueries(0):
563
+ qs = ComputedField.objects.get_for_model(Location)
564
+ with self.assertNumQueries(0):
565
+ listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
566
+ self.assertIsInstance(qs, QuerySet)
567
+ self.assertIsInstance(listing, list)
568
+ self.assertEqual(1, len(listing))
569
+ self.assertQuerysetEqualAndNotEmpty(qs, listing)
462
570
 
463
571
 
464
572
  class CustomFieldDataAPITest(APITestCase):
@@ -482,10 +482,12 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
482
482
  # Assert that the cache is used when calling method a second time
483
483
  with self.assertNumQueries(0):
484
484
  manager_method(Location)
485
+ with self.assertNumQueries(0):
486
+ manager_method(Location, get_queryset=False)
485
487
 
486
488
  # Assert that different models are cached separately
487
489
  with self.assertNumQueries(expected_queries):
488
- manager_method(Rack)
490
+ manager_method(Rack, get_queryset=False)
489
491
  with self.assertNumQueries(0):
490
492
  manager_method(Rack)
491
493
  with self.assertNumQueries(0):
@@ -507,6 +509,8 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
507
509
  manager_method(Location)
508
510
  with self.assertNumQueries(0):
509
511
  manager_method(Location)
512
+ with self.assertNumQueries(0):
513
+ manager_method(Location, get_queryset=False)
510
514
  finally:
511
515
  # Assert that the cache is invalidated on object delete
512
516
  relationship.delete()
@@ -514,6 +518,8 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
514
518
  manager_method(Location)
515
519
  with self.assertNumQueries(0):
516
520
  manager_method(Location)
521
+ with self.assertNumQueries(0):
522
+ manager_method(Location, get_queryset=False)
517
523
 
518
524
  def test_required_related_object_errors(self):
519
525
  """
@@ -1850,8 +1850,18 @@ class SecretTestCase(
1850
1850
  }
1851
1851
 
1852
1852
 
1853
- class SecretsGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
1853
+ class SecretsGroupTestCase(
1854
+ ViewTestCases.OrganizationalObjectViewTestCase,
1855
+ ViewTestCases.BulkEditObjectsViewTestCase,
1856
+ ):
1854
1857
  model = SecretsGroup
1858
+ custom_test_permissions = [
1859
+ "extras.view_secret",
1860
+ "extras.add_secretsgroup",
1861
+ "extras.view_secretsgroup",
1862
+ "extras.add_secretsgroupassociation",
1863
+ "extras.change_secretsgroupassociation",
1864
+ ]
1855
1865
 
1856
1866
  @classmethod
1857
1867
  def setUpTestData(cls):
@@ -1895,6 +1905,108 @@ class SecretsGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
1895
1905
  "secrets_group_associations-MIN_NUM_FORMS": "0",
1896
1906
  "secrets_group_associations-MAX_NUM_FORMS": "1000",
1897
1907
  }
1908
+ cls.bulk_edit_data = {
1909
+ "description": "This is a very detailed new description",
1910
+ }
1911
+
1912
+ def test_create_group_with_valid_secret_association(self):
1913
+ """Test that a SecretsGroup with a valid Secret association saves correctly via the formset."""
1914
+ self.add_permissions(*self.custom_test_permissions)
1915
+ # Create a secret to associate
1916
+ secret = Secret.objects.create(
1917
+ name="AWS_Secret",
1918
+ provider="text-file",
1919
+ parameters={"path": "/tmp"}, # noqa: S108 # hardcoded-temp-file -- false positive
1920
+ )
1921
+
1922
+ form_data = {
1923
+ "name": "test",
1924
+ "description": "test bulk edits",
1925
+ "secrets_group_associations-TOTAL_FORMS": "1",
1926
+ "secrets_group_associations-INITIAL_FORMS": "0",
1927
+ "secrets_group_associations-MIN_NUM_FORMS": "0",
1928
+ "secrets_group_associations-MAX_NUM_FORMS": "1000",
1929
+ "secrets_group_associations-0-secret": secret.pk,
1930
+ "secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
1931
+ "secrets_group_associations-0-secret_type": SecretsGroupSecretTypeChoices.TYPE_PASSWORD,
1932
+ }
1933
+
1934
+ # Submit the form to the "add SecretsGroup" view
1935
+ response = self.client.post(reverse("extras:secretsgroup_add"), data=form_data, follow=True)
1936
+
1937
+ self.assertEqual(response.status_code, 200)
1938
+ self.assertTrue(SecretsGroup.objects.filter(name="test").exists())
1939
+
1940
+ # Checks that the association was created correctly
1941
+ group = SecretsGroup.objects.get(name="test")
1942
+ self.assertEqual(group.secrets_group_associations.count(), 1)
1943
+
1944
+ association = group.secrets_group_associations.first()
1945
+ self.assertEqual(association.secret, secret)
1946
+ self.assertEqual(association.access_type, SecretsGroupAccessTypeChoices.TYPE_HTTP)
1947
+ self.assertEqual(association.secret_type, SecretsGroupSecretTypeChoices.TYPE_PASSWORD)
1948
+
1949
+ def test_create_group_with_invalid_secret_association(self):
1950
+ """Test that invalid Secret association formset raises validation error and does not save."""
1951
+ self.add_permissions(*self.custom_test_permissions)
1952
+ url = reverse("extras:secretsgroup_add")
1953
+
1954
+ form_data = {
1955
+ "name": "Invalid Secrets Group",
1956
+ "description": "Missing required fields",
1957
+ "secrets_group_associations-TOTAL_FORMS": "1",
1958
+ "secrets_group_associations-INITIAL_FORMS": "0",
1959
+ "secrets_group_associations-MIN_NUM_FORMS": "0",
1960
+ "secrets_group_associations-MAX_NUM_FORMS": "1000",
1961
+ "secrets_group_associations-0-secret": "", # invalid
1962
+ "secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
1963
+ "secrets_group_associations-0-secret_type": "", # invalid
1964
+ }
1965
+
1966
+ response = self.client.post(url, data=form_data)
1967
+
1968
+ self.assertEqual(response.status_code, 200)
1969
+
1970
+ # Checks that no new SecretsGroup was created
1971
+ self.assertFalse(SecretsGroup.objects.filter(name="Invalid Secrets Group").exists())
1972
+
1973
+ # Checks that formset errors are raised in the context
1974
+ self.assertFormsetError(
1975
+ response.context["secrets"], form_index=0, field="secret", errors=["This field is required."]
1976
+ )
1977
+
1978
+ def test_create_group_with_deleted_secret_fails_cleanly(self):
1979
+ """
1980
+ Creating a SecretsGroup with a deleted Secret should fail with a formset error.
1981
+ """
1982
+ self.add_permissions(*self.custom_test_permissions)
1983
+
1984
+ secret = Secret.objects.create(name="TempSecret", provider="text-file", parameters={"path": "/tmp"}) # noqa: S108 # hardcoded-temp-file -- false positive
1985
+ secret_pk = secret.pk
1986
+ secret.delete()
1987
+
1988
+ form_data = {
1989
+ "name": "Test Group",
1990
+ "description": "This should not be created",
1991
+ "secrets_group_associations-TOTAL_FORMS": "1",
1992
+ "secrets_group_associations-INITIAL_FORMS": "0",
1993
+ "secrets_group_associations-MIN_NUM_FORMS": "0",
1994
+ "secrets_group_associations-MAX_NUM_FORMS": "1000",
1995
+ "secrets_group_associations-0-secret": secret_pk,
1996
+ "secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
1997
+ "secrets_group_associations-0-secret_type": SecretsGroupSecretTypeChoices.TYPE_PASSWORD,
1998
+ }
1999
+
2000
+ response = self.client.post(reverse("extras:secretsgroup_add"), data=form_data)
2001
+ self.assertEqual(response.status_code, 200)
2002
+
2003
+ self.assertFormsetError(
2004
+ response.context["secrets"],
2005
+ form_index=0,
2006
+ field="secret",
2007
+ errors=["Select a valid choice. That choice is not one of the available choices."],
2008
+ )
2009
+ self.assertFalse(SecretsGroup.objects.filter(name="Test Group").exists())
1898
2010
 
1899
2011
 
1900
2012
  class GraphQLQueriesTestCase(
nautobot/extras/urls.py CHANGED
@@ -6,11 +6,9 @@ from nautobot.extras.models import (
6
6
  CustomField,
7
7
  DynamicGroup,
8
8
  GitRepository,
9
- GraphQLQuery,
10
9
  Job,
11
10
  Note,
12
11
  Relationship,
13
- SecretsGroup,
14
12
  )
15
13
 
16
14
  app_name = "extras"
@@ -24,6 +22,7 @@ router.register("contact-associations", views.ContactAssociationUIViewSet)
24
22
  router.register("custom-links", views.CustomLinkUIViewSet)
25
23
  router.register("export-templates", views.ExportTemplateUIViewSet)
26
24
  router.register("external-integrations", views.ExternalIntegrationUIViewSet)
25
+ router.register("graphql-queries", views.GraphQLQueryUIViewSet)
27
26
  router.register("job-buttons", views.JobButtonUIViewSet)
28
27
  router.register("job-hooks", views.JobHookUIViewSet)
29
28
  router.register("job-queues", views.JobQueueUIViewSet)
@@ -34,6 +33,7 @@ router.register("relationships", views.RelationshipUIViewSet)
34
33
  router.register("roles", views.RoleUIViewSet)
35
34
  router.register("saved-views", views.SavedViewUIViewSet)
36
35
  router.register("secrets", views.SecretUIViewSet)
36
+ router.register("secrets-groups", views.SecretsGroupUIViewSet)
37
37
  router.register("static-group-associations", views.StaticGroupAssociationUIViewSet)
38
38
  router.register("statuses", views.StatusUIViewSet)
39
39
  router.register("tags", views.TagUIViewSet)
@@ -181,37 +181,6 @@ urlpatterns = [
181
181
  views.GitRepositoryDryRunView.as_view(),
182
182
  name="gitrepository_dryrun",
183
183
  ),
184
- # GraphQL Queries
185
- path("graphql-queries/", views.GraphQLQueryListView.as_view(), name="graphqlquery_list"),
186
- path("graphql-queries/add/", views.GraphQLQueryEditView.as_view(), name="graphqlquery_add"),
187
- path(
188
- "graphql-queries/delete/",
189
- views.GraphQLQueryBulkDeleteView.as_view(),
190
- name="GraphQLQuery_bulk_delete",
191
- ),
192
- path("graphql-queries/<uuid:pk>/", views.GraphQLQueryView.as_view(), name="graphqlquery"),
193
- path(
194
- "graphql-queries/<uuid:pk>/edit/",
195
- views.GraphQLQueryEditView.as_view(),
196
- name="graphqlquery_edit",
197
- ),
198
- path(
199
- "graphql-queries/<uuid:pk>/delete/",
200
- views.GraphQLQueryDeleteView.as_view(),
201
- name="graphqlquery_delete",
202
- ),
203
- path(
204
- "graphql-queries/<uuid:pk>/changelog/",
205
- views.ObjectChangeLogView.as_view(),
206
- name="graphqlquery_changelog",
207
- kwargs={"model": GraphQLQuery},
208
- ),
209
- path(
210
- "graphql-queries/<uuid:pk>/notes/",
211
- views.ObjectNotesView.as_view(),
212
- name="graphqlquery_notes",
213
- kwargs={"model": GraphQLQuery},
214
- ),
215
184
  # Image attachments
216
185
  path(
217
186
  "image-attachments/<uuid:pk>/edit/",
@@ -311,24 +280,6 @@ urlpatterns = [
311
280
  views.SecretProviderParametersFormView.as_view(),
312
281
  name="secret_provider_parameters_form",
313
282
  ),
314
- path("secrets-groups/", views.SecretsGroupListView.as_view(), name="secretsgroup_list"),
315
- path("secrets-groups/add/", views.SecretsGroupEditView.as_view(), name="secretsgroup_add"),
316
- path("secrets-groups/delete/", views.SecretsGroupBulkDeleteView.as_view(), name="secretsgroup_bulk_delete"),
317
- path("secrets-groups/<uuid:pk>/", views.SecretsGroupView.as_view(), name="secretsgroup"),
318
- path("secrets-groups/<uuid:pk>/edit/", views.SecretsGroupEditView.as_view(), name="secretsgroup_edit"),
319
- path("secrets-groups/<uuid:pk>/delete/", views.SecretsGroupDeleteView.as_view(), name="secretsgroup_delete"),
320
- path(
321
- "secrets-groups/<uuid:pk>/changelog/",
322
- views.ObjectChangeLogView.as_view(),
323
- name="secretsgroup_changelog",
324
- kwargs={"model": SecretsGroup},
325
- ),
326
- path(
327
- "secrets-groups/<uuid:pk>/notes/",
328
- views.ObjectNotesView.as_view(),
329
- name="secretsgroup_notes",
330
- kwargs={"model": SecretsGroup},
331
- ),
332
283
  ]
333
284
 
334
285
  urlpatterns += router.urls
nautobot/extras/utils.py CHANGED
@@ -275,11 +275,14 @@ def extras_features(*features):
275
275
  """
276
276
 
277
277
  def wrapper(model_class):
278
- # Initialize the model_features store if not already defined
278
+ # Initialize the model_features and feature_models stores if not already defined
279
279
  if "model_features" not in registry:
280
280
  registry["model_features"] = {f: collections.defaultdict(list) for f in EXTRAS_FEATURES}
281
+ if "feature_models" not in registry:
282
+ registry["feature_models"] = {f: [] for f in EXTRAS_FEATURES}
281
283
  for feature in features:
282
284
  if feature in EXTRAS_FEATURES:
285
+ registry["feature_models"][feature].append(model_class)
283
286
  app_label, model_name = model_class._meta.label_lower.split(".")
284
287
  registry["model_features"][feature][app_label].append(model_name)
285
288
  else: