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
@@ -0,0 +1,34 @@
1
+ # Generated by Django 4.2.17 on 2025-01-08 16:59
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+ import nautobot.extras.utils
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ dependencies = [
11
+ ("contenttypes", "0002_remove_content_type_name"),
12
+ ("extras", "0121_alter_team_contacts"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="graphqlquery",
18
+ name="owner_content_type",
19
+ field=models.ForeignKey(
20
+ blank=True,
21
+ default=None,
22
+ limit_choices_to=nautobot.extras.utils.FeatureQuery("graphql_query_owners"),
23
+ null=True,
24
+ on_delete=django.db.models.deletion.CASCADE,
25
+ related_name="graphql_queries",
26
+ to="contenttypes.contenttype",
27
+ ),
28
+ ),
29
+ migrations.AddField(
30
+ model_name="graphqlquery",
31
+ name="owner_object_id",
32
+ field=models.UUIDField(blank=True, default=None, null=True),
33
+ ),
34
+ ]
@@ -370,11 +370,11 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
370
370
 
371
371
  def get_for_model(self, model, exclude_filter_disabled=False):
372
372
  """
373
- Return all CustomFields assigned to the given model.
373
+ Return (and cache) all CustomFields assigned to the given model.
374
374
 
375
375
  Args:
376
- model: The django model to which custom fields are registered
377
- exclude_filter_disabled: Exclude any custom fields which have filter logic disabled
376
+ model (Model): The django model to which custom fields are registered
377
+ exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
378
378
  """
379
379
  concrete_model = model._meta.concrete_model
380
380
  cache_key = (
@@ -521,6 +521,26 @@ class CustomField(
521
521
  def __str__(self):
522
522
  return self.label
523
523
 
524
+ @property
525
+ def choices_cache_key(self):
526
+ return f"nautobot.extras.customfield.choices.{self.pk}"
527
+
528
+ @property
529
+ def choices(self) -> list[str]:
530
+ """
531
+ Cacheable shorthand for retrieving custom_field_choices values associated with this model.
532
+
533
+ Returns:
534
+ list[str]: List of choice values, ordered by weight.
535
+ """
536
+ if self.type not in [CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT]:
537
+ return []
538
+ choices = cache.get(self.choices_cache_key)
539
+ if choices is None:
540
+ choices = list(self.custom_field_choices.order_by("weight", "value").values_list("value", flat=True))
541
+ cache.set(self.choices_cache_key, choices)
542
+ return choices
543
+
524
544
  def save(self, *args, **kwargs):
525
545
  self.clean()
526
546
  super().save(*args, **kwargs)
@@ -679,12 +699,11 @@ class CustomField(
679
699
 
680
700
  # Select or Multi-select
681
701
  else:
682
- choices = [(cfc.value, cfc.value) for cfc in self.custom_field_choices.all()]
683
- default_choice = self.custom_field_choices.filter(value=self.default).first()
702
+ choices = [(value, value) for value in self.choices]
684
703
 
685
704
  # Set the initial value to the first available choice (if any)
686
705
  if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filter_form:
687
- if not required or default_choice is None:
706
+ if not required or self.default not in self.choices:
688
707
  choices = add_blank_choice(choices)
689
708
  field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
690
709
  field = field_class(
@@ -779,17 +798,15 @@ class CustomField(
779
798
 
780
799
  # Validate selected choice
781
800
  elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
782
- if value not in self.custom_field_choices.values_list("value", flat=True):
783
- raise ValidationError(
784
- f"Invalid choice ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
785
- )
801
+ if value not in self.choices:
802
+ raise ValidationError(f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}")
786
803
 
787
804
  elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
788
805
  if isinstance(value, str):
789
806
  value = value.split(",")
790
- if not set(value).issubset(self.custom_field_choices.values_list("value", flat=True)):
807
+ if not set(value).issubset(self.choices):
791
808
  raise ValidationError(
792
- f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
809
+ f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.choices)}"
793
810
  )
794
811
 
795
812
  elif self.required:
@@ -1,7 +1,11 @@
1
1
  """Models for representing external data sources."""
2
2
 
3
+ from contextlib import contextmanager
3
4
  from importlib.util import find_spec
5
+ import logging
4
6
  import os
7
+ import shutil
8
+ import tempfile
5
9
 
6
10
  from django.conf import settings
7
11
  from django.core.exceptions import ValidationError
@@ -12,12 +16,16 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
12
16
  from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
13
17
  from nautobot.core.models.generics import PrimaryModel
14
18
  from nautobot.core.models.validators import EnhancedURLValidator
19
+ from nautobot.core.utils.git import GitRepo
15
20
  from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
16
21
 
22
+ logger = logging.getLogger(__name__)
23
+
17
24
 
18
25
  @extras_features(
19
26
  "config_context_owners",
20
27
  "export_template_owners",
28
+ "graphql_query_owners",
21
29
  "graphql",
22
30
  "job_results",
23
31
  "webhooks",
@@ -167,3 +175,80 @@ class GitRepository(PrimaryModel):
167
175
  if dry_run:
168
176
  return enqueue_git_repository_diff_origin_and_local(self, user)
169
177
  return enqueue_pull_git_repository_and_refresh_data(self, user)
178
+
179
+ @contextmanager
180
+ def clone_to_directory_context(self, path=None, branch=None, head=None, depth=0):
181
+ """
182
+ Context manager to perform a (shallow or full) clone of the Git repository in a temporary directory.
183
+
184
+ Args:
185
+ path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
186
+ branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
187
+ head (str, optional): Git commit hash to check out instead of pulling branch latest.
188
+ depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
189
+
190
+ Returns:
191
+ Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
192
+ """
193
+
194
+ if branch and head:
195
+ raise ValueError("Cannot specify both branch and head")
196
+
197
+ path_name = None
198
+ try:
199
+ path_name = self.clone_to_directory(path=path, branch=branch, head=head, depth=depth)
200
+ yield path_name
201
+ finally:
202
+ # Cleanup the temporary directory
203
+ if path_name:
204
+ self.cleanup_cloned_directory(path_name)
205
+
206
+ def clone_to_directory(self, path=None, branch=None, head=None, depth=0):
207
+ """
208
+ Perform a (shallow or full) clone of the Git repository in a temporary directory.
209
+
210
+ Args:
211
+ path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
212
+ branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
213
+ head (str, optional): Git commit hash to check out instead of pulling branch latest.
214
+ depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
215
+
216
+ Returns:
217
+ Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
218
+ """
219
+ if branch and head:
220
+ raise ValueError("Cannot specify both branch and head")
221
+
222
+ try:
223
+ path_name = tempfile.mkdtemp(dir=path, prefix=self.slug)
224
+ except PermissionError as e:
225
+ logger.error(f"Failed to create temporary directory at {path}: {e}")
226
+ raise e
227
+
228
+ if not branch:
229
+ branch = self.branch
230
+
231
+ try:
232
+ repo_helper = GitRepo(path_name, self.remote_url, depth=depth, branch=branch)
233
+ if head:
234
+ repo_helper.checkout(branch, head)
235
+ except Exception as e:
236
+ logger.error(f"Failed to clone repository {self.name} to {path_name}: {e}")
237
+ raise e
238
+
239
+ logger.info(f"Cloned repository {self.name} to {path_name}")
240
+ return path_name
241
+
242
+ def cleanup_cloned_directory(self, path):
243
+ """
244
+ Cleanup the cloned directory.
245
+
246
+ Args:
247
+ path (str): The absolute directory path to cleanup.
248
+ """
249
+
250
+ try:
251
+ shutil.rmtree(path)
252
+ except OSError as os_error:
253
+ # log error if the cleanup fails
254
+ logger.error(f"Failed to cleanup temporary directory at {path}: {os_error}")
@@ -680,6 +680,21 @@ class GraphQLQuery(
680
680
  SavedViewMixin,
681
681
  BaseModel,
682
682
  ):
683
+ # A Graphql Query *may* be owned by another model, such as a GitRepository, or it may be un-owned
684
+ owner_content_type = models.ForeignKey(
685
+ to=ContentType,
686
+ on_delete=models.CASCADE,
687
+ limit_choices_to=FeatureQuery("graphql_query_owners"),
688
+ default=None,
689
+ null=True,
690
+ blank=True,
691
+ related_name="graphql_queries",
692
+ )
693
+ owner_object_id = models.UUIDField(default=None, null=True, blank=True)
694
+ owner = GenericForeignKey(
695
+ ct_field="owner_content_type",
696
+ fk_field="owner_object_id",
697
+ )
683
698
  name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
684
699
  query = models.TextField()
685
700
  variables = models.JSONField(encoder=DjangoJSONEncoder, default=dict, blank=True)
@@ -261,15 +261,27 @@ class RelationshipModel(models.Model):
261
261
  remote_model = remote_ct.model_class()
262
262
  if remote_model is not None:
263
263
  if not relationship.symmetric:
264
- query_params = {f"{peer_side}_for_associations__relationship": relationship.pk}
265
- resp[side][relationship] = remote_model.objects.filter(**query_params)
264
+ query_params = {
265
+ f"{peer_side}_for_associations__relationship": relationship,
266
+ f"{peer_side}_for_associations__{side}_id": self.pk,
267
+ }
268
+ # Get the related objects for this relationship on the opposite side.
269
+ resp[side][relationship] = remote_model.objects.filter(**query_params).distinct()
266
270
  if not relationship.has_many(peer_side):
267
271
  resp[side][relationship] = resp[side][relationship].first()
268
272
  else:
273
+ side_query_params = {
274
+ f"{peer_side}_for_associations__relationship": relationship,
275
+ f"{peer_side}_for_associations__{side}_id": self.pk,
276
+ }
277
+ peer_side_query_params = {
278
+ f"{side}_for_associations__relationship": relationship,
279
+ f"{side}_for_associations__{peer_side}_id": self.pk,
280
+ }
281
+ # Get the related objects based on the pks we gathered.
269
282
  resp[RelationshipSideChoices.SIDE_PEER][relationship] = remote_model.objects.filter(
270
- Q(source_for_associations__relationship=relationship.pk)
271
- | Q(destination_for_associations__relationship=relationship.pk)
272
- ).exclude(pk=self.pk)
283
+ Q(**side_query_params) | Q(**peer_side_query_params)
284
+ ).distinct()
273
285
  if not relationship.has_many(peer_side):
274
286
  resp[side][relationship] = resp[side][relationship].first()
275
287
  else:
@@ -28,6 +28,7 @@ from nautobot.extras.models import (
28
28
  ComputedField,
29
29
  ContactAssociation,
30
30
  CustomField,
31
+ CustomFieldChoice,
31
32
  DynamicGroup,
32
33
  DynamicGroupMembership,
33
34
  GitRepository,
@@ -97,6 +98,19 @@ def invalidate_models_cache(sender, **kwargs):
97
98
  cache.delete_pattern(f"{manager.keys_for_model.cache_key_prefix}.*")
98
99
 
99
100
 
101
+ @receiver(post_delete, sender=CustomField)
102
+ @receiver(post_delete, sender=CustomFieldChoice)
103
+ @receiver(post_save, sender=CustomFieldChoice)
104
+ @receiver(post_save, sender=CustomField)
105
+ def invalidate_choices_cache(sender, instance, **kwargs):
106
+ """Invalidate the choices cache for CustomFields."""
107
+ with contextlib.suppress(redis.exceptions.ConnectionError):
108
+ if sender is CustomField:
109
+ cache.delete(instance.choices_cache_key)
110
+ else:
111
+ cache.delete(instance.custom_field.choices_cache_key)
112
+
113
+
100
114
  @receiver(post_save, sender=Relationship)
101
115
  @receiver(m2m_changed, sender=Relationship)
102
116
  @receiver(post_delete, sender=Relationship)
@@ -376,7 +390,7 @@ def git_repository_pre_delete(instance, **kwargs):
376
390
  app.control.broadcast("discard_git_repository", repository_slug=instance.slug)
377
391
  # But we don't have an equivalent way to broadcast to any other Django instances.
378
392
  # For now we just delete the one that we have locally and rely on other methods,
379
- # such as the import_jobs() signal that runs on server startup,
393
+ # such as the import_jobs() signal that runs on post migrate,
380
394
  # to clean up other clones as they're encountered.
381
395
  if os.path.isdir(instance.filesystem_path):
382
396
  shutil.rmtree(instance.filesystem_path)
@@ -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 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 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
 
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_nav_tabs %}
@@ -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_buttons %}
@@ -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 perms %}
4
4
  {% load plugins %}
@@ -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
  {% load custom_links %}
4
4
  {% load form_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 title %}{{ object }}{% endblock %}
@@ -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 header %}
@@ -1,63 +1 @@
1
- {% extends 'generic/object_detail.html' %}
2
- {% load buttons %}
3
- {% load plugins %}
4
- {% load perms %}
5
- {% load helpers %}
6
-
7
- {% block content_left_page %}
8
- <div class="panel panel-default">
9
- <div class="panel-heading">
10
- <strong>Relationship</strong>
11
- </div>
12
- <table class="table table-hover panel-body attr-table">
13
- <tr>
14
- <td>Description</td>
15
- <td>{{ object.description | placeholder }}</td>
16
- </tr>
17
- <tr>
18
- <td>Type</td>
19
- <td>{{ object.type }}</td>
20
- </tr>
21
- <tr>
22
- <td>Required On</td>
23
- <td>{{ object.get_required_on_display }}</td>
24
- </tr>
25
- <tr>
26
- <td>On Advanced Tab</td>
27
- <td>{{ object.advanced_ui | render_boolean }}</td>
28
- </tr>
29
- <tr>
30
- <td>Source Content Type</td>
31
- <td>{{ object.source_type }}</td>
32
- </tr>
33
- <tr>
34
- <td>Source Label</td>
35
- <td>{{ object.source_label | placeholder }}</td>
36
- </tr>
37
- <tr>
38
- <td>Source Filter</td>
39
- <td>{% if object.source_filter %}<pre>{{ object.source_filter | render_json }}</pre>{% else %}{{ None | placeholder }}{% endif %}</td>
40
- </tr>
41
- <tr>
42
- <td>Hide on Source Object</td>
43
- <td>{{ object.source_hidden | render_boolean }}</td>
44
- </tr>
45
- <tr>
46
- <td>Destination Content Type</td>
47
- <td>{{ object.destination_type }}</td>
48
- </tr>
49
- <tr>
50
- <td>Destination Label</td>
51
- <td>{{ object.destination_label | placeholder }}</td>
52
- </tr>
53
- <tr>
54
- <td>Destination Filter</td>
55
- <td>{% if object.destination_filter %}<pre>{{ object.destination_filter | render_json }}</pre>{% else %}{{ None | placeholder }}{% endif %}</td>
56
- </tr>
57
- <tr>
58
- <td>Hide on Destination Object</td>
59
- <td>{{ object.destination_hidden | render_boolean }}</td>
60
- </tr>
61
- </table>
62
- </div>
63
- {% endblock content_left_page %}
1
+ {% extends 'generic/object_retrieve.html' %}
@@ -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 buttons %}
3
3
  {% load custom_links %}
4
4
  {% load helpers %}
@@ -1 +1 @@
1
- {% extends 'generic/object_detail.html' %}
1
+ {% extends 'generic/object_retrieve.html' %}
@@ -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 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 %}
@@ -0,0 +1,8 @@
1
+ query {
2
+ devices {
3
+ name
4
+ interfaces {
5
+ name
6
+ }
7
+ }
8
+ }
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
14
14
  SOURCE_DIR = os.path.join(os.path.dirname(__file__), "git_data")
15
15
 
16
16
 
17
- def create_and_populate_git_repository(target_path):
17
+ def create_and_populate_git_repository(target_path, divergent_branch=None):
18
18
  """
19
19
  Create a Git repository in `target_path` and populate it with commits and tags based on contents of `SOURCE_DIR`.
20
20
 
@@ -40,6 +40,9 @@ def create_and_populate_git_repository(target_path):
40
40
  Note that each commit is fully defined by the files in the appropriate subdirectory; if you want a file to exist
41
41
  across multiple separate commits, it must exist in multiple subdirectories. Use of symlinks is encouraged in such
42
42
  a scenario.
43
+
44
+ You can optionally create and check out a divergent branch from the main branch by passing a branch name as the `divergent_branch`.
45
+ This will write a commit to the divergent branch and tag it with the branch name with the `-tag` suffix.
43
46
  """
44
47
  os.makedirs(target_path, exist_ok=True)
45
48
  repo = Repo.init(target_path, initial_branch="main")
@@ -69,6 +72,11 @@ def create_and_populate_git_repository(target_path):
69
72
  # Directory "01-valid-files" --> tag "valid-files" so that we won't break tests if we renumber the directories
70
73
  repo.create_tag(dirname[3:], message=f"Tag based on {dirname} files")
71
74
 
75
+ if divergent_branch:
76
+ repo.create_head(divergent_branch)
77
+ repo.index.commit("divergent-branch")
78
+ repo.create_tag(f"{divergent_branch}-tag", message=f"Tag for divergent branch {divergent_branch}")
79
+
72
80
 
73
81
  if __name__ == "__main__":
74
82
  directory_path = tempfile.TemporaryDirectory().name # pylint: disable=consider-using-with
@@ -6,28 +6,41 @@ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Man
6
6
  from nautobot.extras.models import Role, Status
7
7
 
8
8
 
9
- def create_test_device():
10
- test_uuid = str(uuid.uuid4())
11
- device_role, _ = Role.objects.get_or_create(name="Device Role")
12
- device_ct = ContentType.objects.get_for_model(Device)
13
- device_role.content_types.add(device_ct)
14
- manufacturer = Manufacturer.objects.create(
15
- name=f"Test Manufacturer {test_uuid}",
16
- )
17
- device_type = DeviceType.objects.create(manufacturer=manufacturer, model=f"Test Model {test_uuid}")
18
- location_type = LocationType.objects.create(name=f"Test Location Type {test_uuid}")
19
- location_type.content_types.add(ContentType.objects.get_for_model(Device))
9
+ def create_test_device(name=None, location_name=None, test_uuid=None):
10
+ if not test_uuid:
11
+ test_uuid = str(uuid.uuid4())
12
+ if not name:
13
+ name = f"Test Device {test_uuid}"
14
+ if not location_name:
15
+ location_name = f"Test Location {test_uuid}"
16
+
17
+ location_type, location_type_created = LocationType.objects.get_or_create(name=f"Test Location Type {test_uuid}")
18
+ if location_type_created:
19
+ location_type.content_types.add(ContentType.objects.get_for_model(Device))
20
+ location_type.save()
21
+
20
22
  location_status = Status.objects.get_for_model(Location).first()
21
- location = Location.objects.create(
22
- name=f"Test Location {test_uuid}",
23
+ location, _ = Location.objects.get_or_create(
24
+ name=location_name,
23
25
  status=location_status,
24
26
  location_type=location_type,
25
27
  )
26
- device = Device.objects.create(
27
- name=f"Test Device {test_uuid}",
28
+
29
+ device_role, device_role_created = Role.objects.get_or_create(name="Device Role")
30
+ if device_role_created:
31
+ device_role.content_types.add(ContentType.objects.get_for_model(Device))
32
+ device_role.save()
33
+
34
+ manufacturer, _ = Manufacturer.objects.get_or_create(
35
+ name=f"Test Manufacturer {test_uuid}",
36
+ )
37
+
38
+ device_type, _ = DeviceType.objects.get_or_create(manufacturer=manufacturer, model=f"Test Model {test_uuid}")
39
+
40
+ return Device.objects.create(
41
+ name=name,
28
42
  role=device_role,
29
43
  device_type=device_type,
30
44
  location=location,
31
45
  status=location_status,
32
46
  )
33
- return device