nautobot 2.4.1__py3-none-any.whl → 2.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (406) hide show
  1. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
  2. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  3. nautobot/core/apps/__init__.py +0 -5
  4. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  5. nautobot/core/testing/integration.py +437 -10
  6. nautobot/core/tests/test_jobs.py +34 -2
  7. nautobot/core/utils/git.py +7 -2
  8. nautobot/core/views/generic.py +1 -1
  9. nautobot/core/views/mixins.py +13 -6
  10. nautobot/core/views/utils.py +2 -2
  11. nautobot/dcim/forms.py +12 -0
  12. nautobot/dcim/tables/devices.py +2 -1
  13. nautobot/dcim/templates/dcim/cable.html +1 -1
  14. nautobot/dcim/templates/dcim/device/base.html +1 -1
  15. nautobot/dcim/templates/dcim/device.html +2 -2
  16. nautobot/dcim/templates/dcim/device_component.html +1 -1
  17. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  18. nautobot/dcim/templates/dcim/location.html +1 -1
  19. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  20. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  21. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  22. nautobot/dcim/templates/dcim/platform.html +1 -1
  23. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  24. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  25. nautobot/dcim/templates/dcim/rack.html +1 -1
  26. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  27. nautobot/dcim/templates/dcim/rackreservation.html +2 -2
  28. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  29. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  30. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  31. nautobot/dcim/tests/test_views.py +9 -1
  32. nautobot/dcim/views.py +12 -15
  33. nautobot/extras/api/serializers.py +33 -0
  34. nautobot/extras/api/views.py +11 -3
  35. nautobot/extras/constants.py +1 -0
  36. nautobot/extras/datasources/git.py +125 -0
  37. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  38. nautobot/extras/models/customfields.py +29 -12
  39. nautobot/extras/models/datasources.py +85 -0
  40. nautobot/extras/models/models.py +15 -0
  41. nautobot/extras/models/relationships.py +17 -5
  42. nautobot/extras/signals.py +15 -1
  43. nautobot/extras/templates/extras/computedfield.html +1 -1
  44. nautobot/extras/templates/extras/configcontext.html +1 -1
  45. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  46. nautobot/extras/templates/extras/customfield.html +1 -1
  47. nautobot/extras/templates/extras/customlink.html +1 -1
  48. nautobot/extras/templates/extras/dynamicgroup.html +1 -1
  49. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  50. nautobot/extras/templates/extras/gitrepository.html +1 -1
  51. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  52. nautobot/extras/templates/extras/job_detail.html +1 -1
  53. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  54. nautobot/extras/templates/extras/jobhook.html +1 -1
  55. nautobot/extras/templates/extras/jobresult.html +1 -1
  56. nautobot/extras/templates/extras/objectchange.html +1 -1
  57. nautobot/extras/templates/extras/plugin_detail.html +1 -1
  58. nautobot/extras/templates/extras/relationship.html +1 -63
  59. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  60. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  61. nautobot/extras/templates/extras/secret.html +1 -1
  62. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  63. nautobot/extras/templates/extras/status.html +1 -1
  64. nautobot/extras/templates/extras/tag.html +1 -1
  65. nautobot/extras/templates/extras/webhook.html +1 -1
  66. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  67. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  68. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  69. nautobot/extras/tests/git_helper.py +9 -1
  70. nautobot/extras/tests/integration/__init__.py +29 -16
  71. nautobot/extras/tests/test_api.py +6 -0
  72. nautobot/extras/tests/test_customfields.py +49 -51
  73. nautobot/extras/tests/test_datasources.py +27 -0
  74. nautobot/extras/tests/test_models.py +283 -0
  75. nautobot/extras/tests/test_utils.py +22 -1
  76. nautobot/extras/utils.py +17 -8
  77. nautobot/extras/views.py +55 -12
  78. nautobot/ipam/models.py +8 -2
  79. nautobot/ipam/tables.py +2 -2
  80. nautobot/ipam/templates/ipam/ipaddress.html +1 -1
  81. nautobot/ipam/templates/ipam/prefix.html +1 -1
  82. nautobot/ipam/templates/ipam/rir.html +1 -1
  83. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  84. nautobot/ipam/templates/ipam/service.html +1 -1
  85. nautobot/ipam/templates/ipam/vlan.html +1 -1
  86. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  87. nautobot/ipam/templates/ipam/vrf.html +1 -1
  88. nautobot/ipam/tests/test_models.py +24 -0
  89. nautobot/ipam/tests/test_utils.py +41 -2
  90. nautobot/ipam/utils/__init__.py +18 -11
  91. nautobot/project-static/docs/404.html +87 -12
  92. nautobot/project-static/docs/apps/index.html +87 -12
  93. nautobot/project-static/docs/apps/nautobot-apps.html +87 -12
  94. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
  95. nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
  96. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  97. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  98. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  99. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
  100. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
  101. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
  102. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
  103. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
  104. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
  105. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
  106. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
  107. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
  108. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
  109. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
  110. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
  111. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
  112. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
  113. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
  114. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
  115. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
  116. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
  117. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
  118. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
  119. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
  120. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
  121. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
  122. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
  123. nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
  124. nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
  125. nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
  126. nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
  127. nautobot/project-static/docs/development/apps/api/models/graphql.html +87 -12
  128. nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
  129. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
  130. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
  131. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +87 -12
  132. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
  133. nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
  134. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
  135. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
  136. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
  137. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
  138. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
  139. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
  140. nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
  141. nautobot/project-static/docs/development/apps/api/setup.html +87 -12
  142. nautobot/project-static/docs/development/apps/api/testing.html +87 -12
  143. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
  144. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
  145. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
  146. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
  147. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
  148. nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
  149. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
  150. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
  151. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
  152. nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
  153. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
  154. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
  155. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
  156. nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
  157. nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
  158. nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
  159. nautobot/project-static/docs/development/apps/index.html +87 -12
  160. nautobot/project-static/docs/development/apps/migration/code-updates.html +87 -12
  161. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +87 -12
  162. nautobot/project-static/docs/development/apps/migration/from-v1.html +87 -12
  163. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
  164. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
  165. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
  166. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
  167. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
  168. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
  169. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
  170. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
  171. nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
  172. nautobot/project-static/docs/development/core/application-registry.html +87 -12
  173. nautobot/project-static/docs/development/core/best-practices.html +87 -12
  174. nautobot/project-static/docs/development/core/bootstrap-ui.html +87 -12
  175. nautobot/project-static/docs/development/core/caching.html +87 -12
  176. nautobot/project-static/docs/development/core/controllers.html +87 -12
  177. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +87 -12
  178. nautobot/project-static/docs/development/core/generic-views.html +87 -12
  179. nautobot/project-static/docs/development/core/getting-started.html +87 -12
  180. nautobot/project-static/docs/development/core/homepage.html +87 -12
  181. nautobot/project-static/docs/development/core/index.html +87 -12
  182. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +87 -12
  183. nautobot/project-static/docs/development/core/model-checklist.html +87 -12
  184. nautobot/project-static/docs/development/core/model-features.html +87 -12
  185. nautobot/project-static/docs/development/core/natural-keys.html +87 -12
  186. nautobot/project-static/docs/development/core/navigation-menu.html +87 -12
  187. nautobot/project-static/docs/development/core/release-checklist.html +87 -12
  188. nautobot/project-static/docs/development/core/role-internals.html +87 -12
  189. nautobot/project-static/docs/development/core/settings.html +87 -12
  190. nautobot/project-static/docs/development/core/style-guide.html +87 -12
  191. nautobot/project-static/docs/development/core/templates.html +88 -13
  192. nautobot/project-static/docs/development/core/testing.html +87 -12
  193. nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
  194. nautobot/project-static/docs/development/core/user-preferences.html +87 -12
  195. nautobot/project-static/docs/development/index.html +87 -12
  196. nautobot/project-static/docs/development/jobs/index.html +87 -12
  197. nautobot/project-static/docs/development/jobs/migration/from-v1.html +87 -12
  198. nautobot/project-static/docs/index.html +87 -12
  199. nautobot/project-static/docs/overview/application_stack.html +87 -12
  200. nautobot/project-static/docs/overview/design_philosophy.html +87 -12
  201. nautobot/project-static/docs/release-notes/index.html +87 -12
  202. nautobot/project-static/docs/release-notes/version-1.0.html +87 -12
  203. nautobot/project-static/docs/release-notes/version-1.1.html +87 -12
  204. nautobot/project-static/docs/release-notes/version-1.2.html +87 -12
  205. nautobot/project-static/docs/release-notes/version-1.3.html +87 -12
  206. nautobot/project-static/docs/release-notes/version-1.4.html +87 -12
  207. nautobot/project-static/docs/release-notes/version-1.5.html +87 -12
  208. nautobot/project-static/docs/release-notes/version-1.6.html +87 -12
  209. nautobot/project-static/docs/release-notes/version-2.0.html +87 -12
  210. nautobot/project-static/docs/release-notes/version-2.1.html +87 -12
  211. nautobot/project-static/docs/release-notes/version-2.2.html +87 -12
  212. nautobot/project-static/docs/release-notes/version-2.3.html +87 -12
  213. nautobot/project-static/docs/release-notes/version-2.4.html +277 -12
  214. nautobot/project-static/docs/requirements.txt +1 -1
  215. nautobot/project-static/docs/search/search_index.json +1 -1
  216. nautobot/project-static/docs/sitemap.xml +296 -288
  217. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  218. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +87 -12
  219. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
  220. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +87 -12
  221. nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
  222. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +87 -12
  223. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +87 -12
  224. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
  225. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +90 -15
  226. nautobot/project-static/docs/user-guide/administration/guides/docker.html +87 -12
  227. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +87 -12
  228. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
  229. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +87 -12
  230. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
  231. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +87 -12
  232. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +87 -12
  233. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
  234. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +87 -12
  235. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
  236. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
  237. nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
  238. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +87 -12
  239. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +87 -12
  240. nautobot/project-static/docs/user-guide/administration/installation/services.html +87 -12
  241. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
  242. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
  243. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  244. nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
  245. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
  246. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +87 -12
  247. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
  248. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
  249. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
  250. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
  251. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
  252. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
  253. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
  254. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +87 -12
  255. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
  256. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
  257. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
  258. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
  259. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
  260. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
  261. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
  262. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
  263. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
  264. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
  265. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
  266. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
  267. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
  283. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
  284. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
  285. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
  286. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
  287. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
  288. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
  289. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
  310. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
  311. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
  312. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
  313. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
  314. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
  315. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
  316. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
  317. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
  318. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
  319. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
  320. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
  321. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
  322. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
  323. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
  324. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
  325. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
  326. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
  327. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
  328. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
  329. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
  330. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
  331. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
  332. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
  333. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
  334. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
  335. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
  336. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +87 -12
  337. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
  338. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
  339. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
  340. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
  341. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
  342. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
  343. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
  344. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
  345. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
  346. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +187 -29
  347. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
  348. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
  349. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
  350. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
  351. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
  352. nautobot/project-static/docs/user-guide/index.html +87 -12
  353. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
  354. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
  355. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
  356. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
  357. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +87 -12
  358. nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
  359. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
  360. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
  361. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
  362. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +87 -12
  363. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
  364. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
  365. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
  366. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
  367. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
  368. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
  369. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +87 -12
  370. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +87 -12
  371. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
  372. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
  373. nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
  374. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
  375. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
  376. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
  377. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
  378. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
  379. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
  380. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
  381. nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
  382. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
  383. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
  384. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
  385. nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
  386. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
  387. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
  388. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
  389. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
  390. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
  391. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  392. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  393. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  394. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  395. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  396. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  397. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  398. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/METADATA +5 -5
  399. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/RECORD +404 -397
  400. nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
  401. nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
  402. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  403. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
  404. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
  405. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +0 -0
  406. {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
@@ -1051,7 +1051,7 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, BulkEditAnd
1051
1051
 
1052
1052
  if form.is_valid():
1053
1053
  logger.debug("Form validation was successful")
1054
- return self.send_bulk_edit_objects_to_job(request, form, model)
1054
+ return self.send_bulk_edit_objects_to_job(request, form.cleaned_data, model)
1055
1055
  else:
1056
1056
  logger.debug("Form validation failed")
1057
1057
 
@@ -992,10 +992,9 @@ class BulkEditAndBulkDeleteModelMixin:
992
992
  )
993
993
  return redirect("extras:jobresult", pk=job_result.pk)
994
994
 
995
- def send_bulk_edit_objects_to_job(self, request, form, model):
995
+ def send_bulk_edit_objects_to_job(self, request, form_data, model):
996
996
  """Prepare and enqueue a bulk edit job."""
997
997
  job_model = Job.objects.get_for_class_path(BulkEditObjects.class_path)
998
- form_data = normalize_querydict(request.POST, form)
999
998
  if filterset_class := lookup.get_filterset_for_model(model):
1000
999
  filter_query_params = normalize_querydict(request.GET, filterset=filterset_class())
1001
1000
  else:
@@ -1288,7 +1287,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Bulk
1288
1287
  form = form_class(queryset.model, request.POST, edit_all=edit_all)
1289
1288
  restrict_form_fields(form, request.user)
1290
1289
  if form.is_valid():
1291
- return self.send_bulk_edit_objects_to_job(self.request, form, queryset.model)
1290
+ return self.send_bulk_edit_objects_to_job(self.request, form.cleaned_data, queryset.model)
1292
1291
  else:
1293
1292
  return self.form_invalid(form)
1294
1293
  table = None
@@ -1312,10 +1311,14 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Bulk
1312
1311
 
1313
1312
  class ObjectChangeLogViewMixin(NautobotViewSetMixin):
1314
1313
  """
1315
- UI mixin to list a model's changelog queryset
1314
+ UI mixin to list a model's changelog queryset.
1315
+
1316
+ base_template: Specify to explicitly identify the base object detail template to render.
1317
+ If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
1318
+ will be used, as per `get_base_template()`.
1316
1319
  """
1317
1320
 
1318
- base_template = None
1321
+ base_template: Optional[str] = None
1319
1322
 
1320
1323
  @drf_action(detail=True)
1321
1324
  def changelog(self, request, *args, **kwargs):
@@ -1330,9 +1333,13 @@ class ObjectChangeLogViewMixin(NautobotViewSetMixin):
1330
1333
  class ObjectNotesViewMixin(NautobotViewSetMixin):
1331
1334
  """
1332
1335
  UI Mixin for an Object's Notes.
1336
+
1337
+ base_template: Specify to explicitly identify the base object detail template to render.
1338
+ If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
1339
+ will be used, as per `get_base_template()`.
1333
1340
  """
1334
1341
 
1335
- base_template = None
1342
+ base_template: Optional[str] = None
1336
1343
 
1337
1344
  @drf_action(detail=True)
1338
1345
  def notes(self, request, *args, **kwargs):
@@ -137,10 +137,10 @@ def get_csv_form_fields_from_serializer_class(serializer_class):
137
137
  elif cf.type == CustomFieldTypeChoices.TYPE_DATE:
138
138
  field_info["format"] = mark_safe("<code>YYYY-MM-DD</code>") # noqa: S308
139
139
  elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
140
- field_info["choices"] = {cfc.value: cfc.value for cfc in cf.custom_field_choices.all()}
140
+ field_info["choices"] = {value: value for value in cf.choices}
141
141
  elif cf.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
142
142
  field_info["format"] = mark_safe('<code>"value,value"</code>') # noqa: S308
143
- field_info["choices"] = {cfc.value: cfc.value for cfc in cf.custom_field_choices.all()}
143
+ field_info["choices"] = {value: value for value in cf.choices}
144
144
  fields.append(field_info)
145
145
  continue
146
146
 
nautobot/dcim/forms.py CHANGED
@@ -307,6 +307,17 @@ class LocationTypeFilterForm(NautobotFilterForm):
307
307
  content_types = MultipleContentTypeField(feature="locations", choices_as_strings=True, required=False)
308
308
 
309
309
 
310
+ class LocationTypeBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
311
+ pk = forms.ModelMultipleChoiceField(queryset=LocationType.objects.all(), widget=forms.MultipleHiddenInput())
312
+ description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
313
+ nestable = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
314
+ add_content_types = MultipleContentTypeField(feature="locations", required=False)
315
+ remove_content_types = MultipleContentTypeField(feature="locations", required=False)
316
+
317
+ class Meta:
318
+ nullable_fields = []
319
+
320
+
310
321
  #
311
322
  # Locations
312
323
  #
@@ -784,6 +795,7 @@ class DeviceFamilyForm(NautobotModelForm):
784
795
  fields = [
785
796
  "name",
786
797
  "description",
798
+ "tags",
787
799
  ]
788
800
 
789
801
 
@@ -106,7 +106,8 @@ __all__ = (
106
106
 
107
107
  class PlatformTable(BaseTable):
108
108
  pk = ToggleColumn()
109
- name = tables.LinkColumn()
109
+ name = tables.Column(linkify=True)
110
+ manufacturer = tables.Column(linkify=True)
110
111
  device_count = LinkedCountColumn(
111
112
  viewname="dcim:device_list",
112
113
  url_params={"platform": "pk"},
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block content_left_page %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
  {% load static %}
4
4
 
@@ -214,8 +214,8 @@
214
214
  <tr>
215
215
  <td>Cluster</td>
216
216
  <td>
217
- {% if object.cluster.group %}
218
- {{ object.cluster.group|hyperlinked_object }} /
217
+ {% if object.cluster.cluster_group %}
218
+ {{ object.cluster.cluster_group|hyperlinked_object }} /
219
219
  {% endif %}
220
220
  {{ object.cluster|hyperlinked_object }}
221
221
  </td>
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load buttons %}
3
3
  {% load helpers %}
4
4
  {% load plugins %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load buttons %}
3
3
  {% load plugins %}
4
4
  {% load helpers %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block content_left_page %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block content_left_page %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block extra_breadcrumbs %}
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
  {% load static %}
4
4
 
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load buttons %}
3
3
  {% load helpers %}
4
4
 
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
  {% load static %}
4
4
 
@@ -15,7 +15,7 @@
15
15
  {% with rack=object.rack %}
16
16
  <tr>
17
17
  <td>Location</td>
18
- <td>{% include 'dcim/inc/location_hierarchy.html' with location=object.location %}</td>
18
+ <td>{% include 'dcim/inc/location_hierarchy.html' with location=rack.location %}</td>
19
19
  </tr>
20
20
  <tr>
21
21
  <td>Group</td>
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
2
2
  {% load helpers %}
3
3
 
4
4
  {% block content_left_page %}
@@ -0,0 +1,30 @@
1
+ import uuid
2
+
3
+ from nautobot.core.testing.integration import (
4
+ BulkOperationsTestCases,
5
+ )
6
+ from nautobot.dcim.models import Device
7
+ from nautobot.extras.tests.integration import create_test_device
8
+
9
+
10
+ class DeviceBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCase):
11
+ """
12
+ Test devices bulk edit / delete operations.
13
+ """
14
+
15
+ model_menu_path = ("Devices", "Devices")
16
+ model_base_viewname = "dcim:device"
17
+ model_edit_data = {"serial": "Test serial"}
18
+ model_filter_by = {"location": "Test Location 2"}
19
+ model_class = Device
20
+
21
+ def setup_items(self):
22
+ Device.objects.all().delete()
23
+ test_uuid = str(uuid.uuid4())
24
+
25
+ # Create device for test
26
+ create_test_device("Test Device Integration Test 1", test_uuid=test_uuid)
27
+ create_test_device("Test Device Integration Test 2", test_uuid=test_uuid)
28
+ create_test_device("Test Device Integration Test 3", test_uuid=test_uuid)
29
+ create_test_device("Test Device Integration Test 4", "Test Location 2", test_uuid=test_uuid)
30
+ create_test_device("Test Device Integration Test 5", "Test Location 2", test_uuid=test_uuid)
@@ -0,0 +1,43 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+
3
+ from nautobot.core.testing.integration import (
4
+ BulkOperationsTestCases,
5
+ )
6
+ from nautobot.dcim.models import Device, Location, LocationType
7
+ from nautobot.extras.models import Status
8
+
9
+
10
+ class LocationBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCase):
11
+ """
12
+ Test locations bulk edit / delete operations.
13
+ """
14
+
15
+ model_menu_path = ("Organization", "Locations")
16
+ model_base_viewname = "dcim:location"
17
+ model_edit_data = {"description": "Test description"}
18
+ model_filter_by = {"location_type": "External"}
19
+ model_class = Location
20
+
21
+ def setup_items(self):
22
+ Location.objects.all().delete()
23
+
24
+ # Create locations for test
25
+ self.create_location("Test Location Integration Test 1")
26
+ self.create_location("Test Location Integration Test 2")
27
+ self.create_location("Test Location Integration Test 3")
28
+ self.create_location("Test Location Integration Test 4", "External")
29
+ self.create_location("Test Location Integration Test 5", "External")
30
+
31
+ @staticmethod
32
+ def create_location(location_name, location_type="Internal"):
33
+ location_type, location_type_created = LocationType.objects.get_or_create(name=location_type)
34
+ if location_type_created:
35
+ location_type.content_types.add(ContentType.objects.get_for_model(Device))
36
+ location_type.save()
37
+
38
+ location_status = Status.objects.get_for_model(Location).first()
39
+ Location.objects.get_or_create(
40
+ name=location_name,
41
+ status=location_status,
42
+ location_type=location_type,
43
+ )
@@ -161,7 +161,7 @@ def create_test_device(name):
161
161
  return device
162
162
 
163
163
 
164
- class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
164
+ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases.BulkEditObjectsViewTestCase):
165
165
  model = LocationType
166
166
  sort_on_field = "nestable"
167
167
 
@@ -194,6 +194,13 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
194
194
  "nestable": True,
195
195
  }
196
196
 
197
+ cls.bulk_edit_data = {
198
+ "description": "A generic description",
199
+ "add_content_types": [
200
+ ContentType.objects.get_for_model(CircuitTermination).pk,
201
+ ],
202
+ }
203
+
197
204
  def _get_queryset(self):
198
205
  return super()._get_queryset().order_by("-last_updated")
199
206
 
@@ -743,6 +750,7 @@ class DeviceFamilyTestCase(ViewTestCases.PrimaryObjectViewTestCase):
743
750
  cls.form_data = {
744
751
  "name": "New Device Family",
745
752
  "description": "A new device family",
753
+ "tags": [t.pk for t in Tag.objects.get_for_model(DeviceFamily)],
746
754
  }
747
755
  cls.bulk_edit_data = {
748
756
  "description": "A new device family",
nautobot/dcim/views.py CHANGED
@@ -40,7 +40,7 @@ from nautobot.core.ui.choices import SectionChoices
40
40
  from nautobot.core.utils.lookup import get_form_for_model
41
41
  from nautobot.core.utils.permissions import get_permission_for_model
42
42
  from nautobot.core.utils.requests import normalize_querydict
43
- from nautobot.core.views import generic, mixins as view_mixins
43
+ from nautobot.core.views import generic
44
44
  from nautobot.core.views.mixins import (
45
45
  GetReturnURLMixin,
46
46
  ObjectBulkDestroyViewMixin,
@@ -213,21 +213,13 @@ class BaseDeviceComponentTemplatesBulkRenameView(generic.BulkRenameView):
213
213
  #
214
214
 
215
215
 
216
- class LocationTypeUIViewSet(
217
- view_mixins.ObjectDetailViewMixin,
218
- view_mixins.ObjectListViewMixin,
219
- view_mixins.ObjectEditViewMixin,
220
- view_mixins.ObjectDestroyViewMixin,
221
- view_mixins.ObjectBulkDestroyViewMixin,
222
- view_mixins.ObjectBulkCreateViewMixin, # 3.0 TODO: remove this mixin as it's no longer used
223
- view_mixins.ObjectChangeLogViewMixin,
224
- view_mixins.ObjectNotesViewMixin,
225
- ):
216
+ class LocationTypeUIViewSet(NautobotUIViewSet):
226
217
  queryset = LocationType.objects.all()
227
218
  filterset_class = filters.LocationTypeFilterSet
228
219
  filterset_form_class = forms.LocationTypeFilterForm
229
220
  table_class = tables.LocationTypeTable
230
221
  form_class = forms.LocationTypeForm
222
+ bulk_update_form_class = forms.LocationTypeBulkEditForm
231
223
  serializer_class = serializers.LocationSerializer
232
224
 
233
225
  object_detail_content = object_detail.ObjectDetailContent(
@@ -1755,16 +1747,21 @@ class DeviceListView(generic.ObjectListView):
1755
1747
 
1756
1748
  class DeviceView(generic.ObjectView):
1757
1749
  queryset = Device.objects.select_related(
1750
+ "cluster__cluster_group",
1751
+ "controller_managed_device_group__controller",
1752
+ "device_redundancy_group",
1753
+ "device_type__device_family",
1758
1754
  "location",
1759
- "rack__rack_group",
1760
- "tenant__tenant_group",
1761
- "role",
1762
1755
  "platform",
1763
1756
  "primary_ip4",
1764
1757
  "primary_ip6",
1758
+ "rack__rack_group",
1759
+ "role",
1760
+ "secrets_group",
1765
1761
  "software_version",
1766
1762
  "status",
1767
- )
1763
+ "tenant__tenant_group",
1764
+ ).prefetch_related("images", "software_image_files")
1768
1765
 
1769
1766
  object_detail_content = object_detail.ObjectDetailContent(
1770
1767
  extra_buttons=(
@@ -480,11 +480,32 @@ class GitRepositorySerializer(TaggedModelSerializerMixin, NautobotModelSerialize
480
480
 
481
481
  class GraphQLQuerySerializer(ValidatedModelSerializer, NotesSerializerMixin):
482
482
  variables = serializers.DictField(read_only=True)
483
+ owner_content_type = ContentTypeField(
484
+ queryset=ContentType.objects.filter(FeatureQuery("graphql_query_owners").get_query()),
485
+ required=False,
486
+ allow_null=True,
487
+ default=None,
488
+ )
489
+ owner = serializers.SerializerMethodField(read_only=True)
483
490
 
484
491
  class Meta:
485
492
  model = GraphQLQuery
486
493
  fields = "__all__"
487
494
 
495
+ @extend_schema_field(
496
+ PolymorphicProxySerializer(
497
+ component_name="GraphQLQueryOwner",
498
+ resource_type_field_name="object_type",
499
+ serializers=lambda: nested_serializers_for_models(FeatureQuery("graphql_query_owners").list_subclasses()),
500
+ allow_null=True,
501
+ )
502
+ )
503
+ def get_owner(self, obj):
504
+ if obj.owner is None:
505
+ return None
506
+ depth = get_nested_serializer_depth(self)
507
+ return return_nested_serializer_data_based_on_depth(self, depth, obj, obj.owner, "owner")
508
+
488
509
 
489
510
  class GraphQLQueryInputSerializer(serializers.Serializer):
490
511
  variables = serializers.DictField(allow_null=True, default={})
@@ -1148,3 +1169,15 @@ class WebhookSerializer(ValidatedModelSerializer, NotesSerializerMixin):
1148
1169
  raise serializers.ValidationError(conflicts)
1149
1170
 
1150
1171
  return validated_attrs
1172
+
1173
+
1174
+ #
1175
+ # More Git repositories
1176
+ #
1177
+
1178
+
1179
+ class GitRepositorySyncResponseSerializer(serializers.Serializer):
1180
+ """Serializer representing responses from the GitRepository.sync() POST endpoint."""
1181
+
1182
+ message = serializers.CharField(read_only=True)
1183
+ job_result = JobResultSerializer(read_only=True)
@@ -425,7 +425,7 @@ class GitRepositoryViewSet(NautobotModelViewSet):
425
425
  serializer_class = serializers.GitRepositorySerializer
426
426
  filterset_class = filters.GitRepositoryFilterSet
427
427
 
428
- @extend_schema(methods=["post"], request=serializers.GitRepositorySerializer)
428
+ @extend_schema(methods=["post"], responses={"200": serializers.GitRepositorySyncResponseSerializer}, request=None)
429
429
  # Since we are explicitly checking for `extras:change_gitrepository` in the API sync() method
430
430
  # We explicitly set the permission_classes to IsAuthenticated in the @action decorator
431
431
  # bypassing the default DRF permission check for `extras:add_gitrepository` and the permission check fall through to the function itself.
@@ -441,8 +441,16 @@ class GitRepositoryViewSet(NautobotModelViewSet):
441
441
  raise CeleryWorkerNotRunningException()
442
442
 
443
443
  repository = get_object_or_404(GitRepository, id=pk)
444
- repository.sync(user=request.user)
445
- return Response({"message": f"Repository {repository} sync job added to queue."})
444
+ job_result = repository.sync(user=request.user)
445
+
446
+ data = {
447
+ # Kept message for backward compatibility for now
448
+ "message": f"Repository {repository} sync job added to queue.",
449
+ "job_result": job_result,
450
+ }
451
+
452
+ serializer = serializers.GitRepositorySyncResponseSerializer(data, context={"request": request})
453
+ return Response(serializer.data, status=status.HTTP_200_OK)
446
454
 
447
455
 
448
456
  #
@@ -11,6 +11,7 @@ EXTRAS_FEATURES = [
11
11
  "dynamic_groups",
12
12
  "export_template_owners",
13
13
  "export_templates",
14
+ "graphql_query_owners",
14
15
  "graphql",
15
16
  "job_results", # No longer used
16
17
  "locations",
@@ -30,6 +30,7 @@ from nautobot.extras.models import (
30
30
  DynamicGroup,
31
31
  ExportTemplate,
32
32
  GitRepository,
33
+ GraphQLQuery,
33
34
  Job,
34
35
  JobQueue,
35
36
  JobResult,
@@ -935,6 +936,120 @@ def delete_git_export_templates(repository_record, job_result, preserve=None):
935
936
  job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="export templates")
936
937
 
937
938
 
939
+ #
940
+ # GraphQL handling
941
+ #
942
+
943
+
944
+ def refresh_git_graphql_queries(repository_record, job_result, delete=False):
945
+ """Callback function for GitRepository updates - refresh all GraphQLQuery managed by this repository."""
946
+ if "extras.graphqlquery" in repository_record.provided_contents and not delete:
947
+ update_git_graphql_queries(repository_record, job_result)
948
+ else:
949
+ delete_git_graphql_queries(repository_record, job_result)
950
+
951
+
952
+ logger = logging.getLogger(__name__)
953
+
954
+
955
+ def update_git_graphql_queries(repository_record, job_result):
956
+ """Refresh any GraphQL queries provided by this Git repository."""
957
+ graphql_query_path = os.path.join(repository_record.filesystem_path, "graphql_queries")
958
+ git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
959
+ graphql_queries = []
960
+
961
+ if os.path.isdir(graphql_query_path):
962
+ for file in os.listdir(graphql_query_path):
963
+ file_path = os.path.join(graphql_query_path, file)
964
+ if not os.path.isfile(file_path):
965
+ continue
966
+
967
+ # Remove `.gql` extension from the name if it exists
968
+ query_name = file.rsplit(".gql", 1)[0] if file.endswith(".gql") else file
969
+
970
+ try:
971
+ with open(file_path, "r") as fd:
972
+ query_content = fd.read().strip()
973
+
974
+ graphql_query, created = GraphQLQuery.objects.get_or_create(
975
+ name=query_name,
976
+ owner_content_type=git_repository_content_type,
977
+ owner_object_id=repository_record.pk,
978
+ defaults={"query": query_content},
979
+ )
980
+ modified = graphql_query.query != query_content
981
+ graphql_queries.append(query_name)
982
+ # Only attempt to update if the content has changed
983
+ if modified:
984
+ try:
985
+ graphql_query.query = query_content
986
+ graphql_query.validated_save()
987
+ msg = (
988
+ f"Successfully created GraphQL query: {query_name}"
989
+ if created
990
+ else f"Successfully updated GraphQL query: {query_name}"
991
+ )
992
+ logger.info(msg)
993
+ job_result.log(
994
+ msg, obj=graphql_query, level_choice=LogLevelChoices.LOG_INFO, grouping="graphql queries"
995
+ )
996
+ except Exception as exc:
997
+ # Log validation error and retain the existing query
998
+ error_msg = (
999
+ f"Invalid GraphQL syntax for query '{query_name}'. "
1000
+ f"Retaining the existing query. Error: {exc}"
1001
+ )
1002
+ logger.error(error_msg)
1003
+ job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
1004
+ continue
1005
+ else:
1006
+ msg = f"No changes to GraphQL query: {query_name}"
1007
+ logger.info(msg)
1008
+ job_result.log(
1009
+ msg, obj=graphql_query, level_choice=LogLevelChoices.LOG_INFO, grouping="graphql queries"
1010
+ )
1011
+
1012
+ except Exception as exc:
1013
+ # Check if a query with the same name already exists
1014
+ existing_query = GraphQLQuery.objects.filter(name=query_name).first()
1015
+ if existing_query and existing_query.owner_object_id != repository_record.pk:
1016
+ error_msg = (
1017
+ f"GraphQL query '{query_name}' already exists "
1018
+ f"Please rename the query in the repository and try again."
1019
+ )
1020
+ else:
1021
+ error_msg = f"Error processing GraphQL query file '{file}': {exc}"
1022
+
1023
+ # Log the error
1024
+ logger.error(error_msg)
1025
+ job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
1026
+
1027
+ # Delete any queries not in the preserved list
1028
+ delete_git_graphql_queries(repository_record, job_result, preserve=graphql_queries)
1029
+
1030
+
1031
+ def delete_git_graphql_queries(repository_record, job_result, preserve=None):
1032
+ """Delete GraphQL queries owned by the given Git repository that are not in the preserve list."""
1033
+ git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
1034
+ if preserve is None:
1035
+ preserve = []
1036
+
1037
+ for graphql_query in GraphQLQuery.objects.filter(
1038
+ owner_content_type=git_repository_content_type,
1039
+ owner_object_id=repository_record.pk,
1040
+ ):
1041
+ if graphql_query.name not in preserve:
1042
+ try:
1043
+ graphql_query.delete()
1044
+ msg = f"Deleted GraphQL query: {graphql_query.name}"
1045
+ logger.warning(msg)
1046
+ job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="graphql queries")
1047
+ except Exception as exc:
1048
+ error_msg = f"Unable to delete '{graphql_query.name}': {exc}"
1049
+ logger.error(error_msg)
1050
+ job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
1051
+
1052
+
938
1053
  # Register built-in callbacks for data types potentially provided by a GitRepository
939
1054
  register_datasource_contents(
940
1055
  [
@@ -978,5 +1093,15 @@ register_datasource_contents(
978
1093
  callback=refresh_git_export_templates,
979
1094
  ),
980
1095
  ),
1096
+ (
1097
+ "extras.gitrepository",
1098
+ DatasourceContent(
1099
+ name="graphql queries",
1100
+ content_identifier="extras.graphqlquery",
1101
+ icon="mdi-graphql",
1102
+ weight=400,
1103
+ callback=refresh_git_graphql_queries,
1104
+ ),
1105
+ ),
981
1106
  ]
982
1107
  )