nautobot 2.3.8__py3-none-any.whl → 2.3.9__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 (318) hide show
  1. nautobot/apps/tables.py +2 -0
  2. nautobot/core/forms/__init__.py +4 -0
  3. nautobot/core/forms/fields.py +32 -0
  4. nautobot/core/jobs/__init__.py +24 -8
  5. nautobot/core/models/tree_queries.py +8 -0
  6. nautobot/core/settings.py +7 -0
  7. nautobot/core/settings.yaml +10 -0
  8. nautobot/core/signals.py +5 -4
  9. nautobot/core/templates/nautobot_config.py.j2 +4 -0
  10. nautobot/dcim/forms.py +30 -27
  11. nautobot/dcim/models/device_components.py +5 -0
  12. nautobot/dcim/tables/devices.py +4 -2
  13. nautobot/dcim/tests/test_models.py +16 -0
  14. nautobot/extras/context_managers.py +7 -8
  15. nautobot/extras/datasources/__init__.py +2 -0
  16. nautobot/extras/datasources/git.py +30 -49
  17. nautobot/extras/datasources/registry.py +2 -2
  18. nautobot/extras/jobs.py +17 -5
  19. nautobot/extras/models/datasources.py +6 -0
  20. nautobot/extras/models/groups.py +47 -33
  21. nautobot/extras/models/jobs.py +1 -1
  22. nautobot/extras/plugins/__init__.py +165 -0
  23. nautobot/extras/templates/extras/plugin_detail.html +33 -0
  24. nautobot/extras/tests/test_context_managers.py +16 -7
  25. nautobot/extras/tests/test_datasources.py +88 -1
  26. nautobot/extras/tests/test_dynamicgroups.py +12 -0
  27. nautobot/extras/tests/test_plugins.py +94 -0
  28. nautobot/extras/views.py +3 -1
  29. nautobot/project-static/docs/404.html +24 -3
  30. nautobot/project-static/docs/apps/index.html +24 -3
  31. nautobot/project-static/docs/apps/nautobot-apps.html +24 -3
  32. nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js → bundle.83f73b43.min.js} +2 -2
  33. nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js.map → bundle.83f73b43.min.js.map} +3 -3
  34. nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css +1 -0
  35. nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css.map +1 -0
  36. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +24 -3
  37. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +24 -3
  38. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +24 -3
  39. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +24 -3
  40. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +24 -3
  41. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +24 -3
  42. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -3
  43. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +24 -3
  44. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +24 -3
  45. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +24 -3
  46. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +24 -3
  47. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +24 -3
  48. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +24 -3
  49. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +49 -6
  50. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +24 -3
  51. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +24 -3
  52. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +24 -3
  53. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +138 -3
  54. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +24 -3
  55. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +24 -3
  56. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +24 -3
  57. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +24 -3
  58. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +24 -3
  59. nautobot/project-static/docs/development/apps/api/configuration-view.html +24 -3
  60. nautobot/project-static/docs/development/apps/api/database-backend-config.html +24 -3
  61. nautobot/project-static/docs/development/apps/api/models/django-admin.html +24 -3
  62. nautobot/project-static/docs/development/apps/api/models/global-search.html +24 -3
  63. nautobot/project-static/docs/development/apps/api/models/graphql.html +24 -3
  64. nautobot/project-static/docs/development/apps/api/models/index.html +24 -3
  65. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +24 -3
  66. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +24 -3
  67. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +24 -3
  68. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +24 -3
  69. nautobot/project-static/docs/development/apps/api/platform-features/index.html +24 -3
  70. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +24 -3
  71. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +24 -3
  72. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +24 -3
  73. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +27 -6
  74. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +8823 -0
  75. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +27 -6
  76. nautobot/project-static/docs/development/apps/api/prometheus.html +24 -3
  77. nautobot/project-static/docs/development/apps/api/setup.html +33 -11
  78. nautobot/project-static/docs/development/apps/api/testing.html +24 -3
  79. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +24 -3
  80. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +24 -3
  81. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +24 -3
  82. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +24 -3
  83. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +24 -3
  84. nautobot/project-static/docs/development/apps/api/views/base-template.html +24 -3
  85. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +24 -3
  86. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +24 -3
  87. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +24 -3
  88. nautobot/project-static/docs/development/apps/api/views/index.html +24 -3
  89. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +24 -3
  90. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -3
  91. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +24 -3
  92. nautobot/project-static/docs/development/apps/api/views/notes.html +24 -3
  93. nautobot/project-static/docs/development/apps/api/views/rest-api.html +24 -3
  94. nautobot/project-static/docs/development/apps/api/views/urls.html +24 -3
  95. nautobot/project-static/docs/development/apps/index.html +24 -3
  96. nautobot/project-static/docs/development/apps/migration/code-updates.html +24 -3
  97. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +24 -3
  98. nautobot/project-static/docs/development/apps/migration/from-v1.html +24 -3
  99. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +24 -3
  100. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +24 -3
  101. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +24 -3
  102. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +24 -3
  103. nautobot/project-static/docs/development/apps/porting-from-netbox.html +24 -3
  104. nautobot/project-static/docs/development/core/application-registry.html +24 -3
  105. nautobot/project-static/docs/development/core/best-practices.html +24 -3
  106. nautobot/project-static/docs/development/core/bootstrap-ui.html +24 -3
  107. nautobot/project-static/docs/development/core/caching.html +24 -3
  108. nautobot/project-static/docs/development/core/controllers.html +24 -3
  109. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +24 -3
  110. nautobot/project-static/docs/development/core/generic-views.html +24 -3
  111. nautobot/project-static/docs/development/core/getting-started.html +24 -3
  112. nautobot/project-static/docs/development/core/homepage.html +24 -3
  113. nautobot/project-static/docs/development/core/index.html +24 -3
  114. nautobot/project-static/docs/development/core/model-checklist.html +24 -3
  115. nautobot/project-static/docs/development/core/model-features.html +24 -3
  116. nautobot/project-static/docs/development/core/natural-keys.html +24 -3
  117. nautobot/project-static/docs/development/core/navigation-menu.html +24 -3
  118. nautobot/project-static/docs/development/core/release-checklist.html +24 -3
  119. nautobot/project-static/docs/development/core/role-internals.html +24 -3
  120. nautobot/project-static/docs/development/core/settings.html +24 -3
  121. nautobot/project-static/docs/development/core/style-guide.html +24 -3
  122. nautobot/project-static/docs/development/core/templates.html +24 -3
  123. nautobot/project-static/docs/development/core/testing.html +24 -3
  124. nautobot/project-static/docs/development/core/user-preferences.html +24 -3
  125. nautobot/project-static/docs/development/index.html +24 -3
  126. nautobot/project-static/docs/development/jobs/index.html +24 -3
  127. nautobot/project-static/docs/development/jobs/migration/from-v1.html +24 -3
  128. nautobot/project-static/docs/index.html +24 -3
  129. nautobot/project-static/docs/objects.inv +0 -0
  130. nautobot/project-static/docs/overview/application_stack.html +24 -3
  131. nautobot/project-static/docs/overview/design_philosophy.html +24 -3
  132. nautobot/project-static/docs/release-notes/index.html +24 -3
  133. nautobot/project-static/docs/release-notes/version-1.0.html +24 -3
  134. nautobot/project-static/docs/release-notes/version-1.1.html +24 -3
  135. nautobot/project-static/docs/release-notes/version-1.2.html +24 -3
  136. nautobot/project-static/docs/release-notes/version-1.3.html +24 -3
  137. nautobot/project-static/docs/release-notes/version-1.4.html +24 -3
  138. nautobot/project-static/docs/release-notes/version-1.5.html +24 -3
  139. nautobot/project-static/docs/release-notes/version-1.6.html +24 -3
  140. nautobot/project-static/docs/release-notes/version-2.0.html +24 -3
  141. nautobot/project-static/docs/release-notes/version-2.1.html +24 -3
  142. nautobot/project-static/docs/release-notes/version-2.2.html +24 -3
  143. nautobot/project-static/docs/release-notes/version-2.3.html +285 -114
  144. nautobot/project-static/docs/requirements.txt +1 -1
  145. nautobot/project-static/docs/search/search_index.json +1 -1
  146. nautobot/project-static/docs/sitemap.xml +273 -269
  147. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  148. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +24 -3
  149. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +24 -3
  150. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +24 -3
  151. nautobot/project-static/docs/user-guide/administration/configuration/index.html +24 -3
  152. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +24 -3
  153. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -4
  154. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +24 -3
  155. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +24 -3
  156. nautobot/project-static/docs/user-guide/administration/guides/docker.html +24 -3
  157. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +24 -3
  158. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +24 -3
  159. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +24 -3
  160. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +24 -3
  161. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +24 -3
  162. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +24 -3
  163. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +24 -3
  164. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +24 -3
  165. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +24 -3
  166. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +24 -3
  167. nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -3
  168. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +24 -3
  169. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +24 -3
  170. nautobot/project-static/docs/user-guide/administration/installation/services.html +24 -3
  171. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +24 -3
  172. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +24 -3
  173. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -3
  174. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +24 -3
  175. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +24 -3
  176. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +24 -3
  177. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +24 -3
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +24 -3
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +24 -3
  180. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +24 -3
  181. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +24 -3
  182. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +24 -3
  183. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +24 -3
  184. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +24 -3
  185. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +24 -3
  186. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +24 -3
  187. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +24 -3
  188. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +24 -3
  189. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +24 -3
  190. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +24 -3
  191. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +24 -3
  192. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +24 -3
  193. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +24 -3
  194. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +24 -3
  195. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +24 -3
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +24 -3
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +24 -3
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +24 -3
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +24 -3
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +24 -3
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +24 -3
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +24 -3
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +24 -3
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +24 -3
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +24 -3
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +24 -3
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +24 -3
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +24 -3
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +24 -3
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +24 -3
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +24 -3
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +24 -3
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +24 -3
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +24 -3
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +24 -3
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +24 -3
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +24 -3
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +24 -3
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +24 -3
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +24 -3
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +24 -3
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +24 -3
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +24 -3
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -3
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +24 -3
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +24 -3
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +24 -3
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +24 -3
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +24 -3
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +24 -3
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +24 -3
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +24 -3
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +24 -3
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +24 -3
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +24 -3
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +24 -3
  237. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +24 -3
  238. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +24 -3
  239. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +24 -3
  240. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +24 -3
  241. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +24 -3
  242. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +24 -3
  243. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +24 -3
  244. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +24 -3
  245. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +24 -3
  246. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +24 -3
  247. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +24 -3
  248. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +24 -3
  249. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +24 -3
  250. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +24 -3
  251. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +24 -3
  252. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +24 -3
  253. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +24 -3
  254. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +24 -3
  255. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +24 -3
  256. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +24 -3
  257. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +24 -3
  258. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +24 -3
  259. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +24 -3
  260. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +24 -3
  261. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +24 -3
  262. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +24 -3
  263. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +24 -3
  264. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +24 -3
  265. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +24 -3
  266. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +24 -3
  267. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +24 -3
  268. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +24 -3
  269. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +24 -3
  270. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +24 -3
  271. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +24 -3
  272. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +24 -3
  273. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +24 -3
  274. nautobot/project-static/docs/user-guide/index.html +24 -3
  275. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +24 -3
  276. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +24 -3
  277. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +24 -3
  278. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +24 -3
  279. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +24 -3
  280. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +24 -3
  281. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +24 -3
  282. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +24 -3
  283. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +24 -3
  284. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +24 -3
  285. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +24 -3
  286. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +24 -3
  287. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -3
  288. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +24 -3
  289. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +24 -3
  290. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +24 -3
  291. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +24 -3
  292. nautobot/project-static/docs/user-guide/platform-functionality/note.html +24 -3
  293. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +24 -3
  294. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +24 -3
  295. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +24 -3
  296. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +24 -3
  297. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +24 -3
  298. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +24 -3
  299. nautobot/project-static/docs/user-guide/platform-functionality/role.html +24 -3
  300. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +24 -3
  301. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +24 -3
  302. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +24 -3
  303. nautobot/project-static/docs/user-guide/platform-functionality/status.html +24 -3
  304. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +24 -3
  305. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +24 -3
  306. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +24 -3
  307. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +24 -3
  308. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +24 -3
  309. nautobot/project-static/js/forms.js +41 -5
  310. nautobot/virtualization/tables.py +1 -1
  311. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/METADATA +2 -2
  312. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/RECORD +316 -315
  313. nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +0 -1
  314. nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +0 -1
  315. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/LICENSE.txt +0 -0
  316. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/NOTICE +0 -0
  317. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/WHEEL +0 -0
  318. {nautobot-2.3.8.dist-info → nautobot-2.3.9.dist-info}/entry_points.txt +0 -0
nautobot/apps/tables.py CHANGED
@@ -15,6 +15,7 @@ from nautobot.core.tables import (
15
15
  TagColumn,
16
16
  ToggleColumn,
17
17
  )
18
+ from nautobot.extras.plugins import TableExtension
18
19
  from nautobot.extras.tables import RoleTableMixin, StatusTableMixin
19
20
 
20
21
  __all__ = (
@@ -32,5 +33,6 @@ __all__ = (
32
33
  "RoleTableMixin",
33
34
  "StatusTableMixin",
34
35
  "TagColumn",
36
+ "TableExtension",
35
37
  "ToggleColumn",
36
38
  )
@@ -7,6 +7,8 @@ from nautobot.core.forms.constants import (
7
7
  NUMERIC_EXPANSION_PATTERN,
8
8
  )
9
9
  from nautobot.core.forms.fields import (
10
+ AutoPositionField,
11
+ AutoPositionPatternField,
10
12
  CommentField,
11
13
  CSVChoiceField,
12
14
  CSVContentTypeField,
@@ -80,6 +82,8 @@ __all__ = (
80
82
  "ALPHANUMERIC_EXPANSION_PATTERN",
81
83
  "APISelect",
82
84
  "APISelectMultiple",
85
+ "AutoPositionField",
86
+ "AutoPositionPatternField",
83
87
  "BOOLEAN_CHOICES",
84
88
  "BOOLEAN_WITH_BLANK_CHOICES",
85
89
  "BootstrapMixin",
@@ -444,6 +444,38 @@ class SlugField(django_forms.SlugField):
444
444
  self.widget.attrs["slug-source"] = slug_source
445
445
 
446
446
 
447
+ class AutoPositionField(django_forms.CharField):
448
+ def __init__(self, source="name", *args, **kwargs):
449
+ """
450
+ Instantiate a AutoPositionField.
451
+
452
+ Args:
453
+ source (str, tuple): Name of the field (or a list of field names) that will be used to suggest a position.
454
+ """
455
+ kwargs.setdefault("label", "Position")
456
+ kwargs.setdefault("widget", forms.SlugWidget)
457
+ super().__init__(*args, **kwargs)
458
+ if isinstance(source, (tuple, list)):
459
+ source = " ".join(source)
460
+ self.widget.attrs["source"] = source
461
+
462
+
463
+ class AutoPositionPatternField(ExpandableNameField):
464
+ def __init__(self, source="name_pattern", *args, **kwargs):
465
+ """
466
+ Instantiate a AutoPositionPatternField.
467
+
468
+ Args:
469
+ source (str, tuple): Name pattern of the field (or a list of field names) that will be used to suggest a position pattern.
470
+ """
471
+ kwargs.setdefault("label", "Position")
472
+ kwargs.setdefault("widget", forms.SlugWidget)
473
+ super().__init__(*args, **kwargs)
474
+ if isinstance(source, (tuple, list)):
475
+ source = " ".join(source)
476
+ self.widget.attrs["source"] = source
477
+
478
+
447
479
  class DynamicModelChoiceMixin:
448
480
  """
449
481
  :param display_field: The name of the attribute of an API response object to display in the selection list
@@ -19,7 +19,12 @@ from nautobot.core.jobs.cleanup import LogsCleanup
19
19
  from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
20
20
  from nautobot.core.utils.lookup import get_filterset_for_model
21
21
  from nautobot.core.utils.requests import get_filterable_params_from_filter_params
22
- from nautobot.extras.datasources import ensure_git_repository, git_repository_dry_run, refresh_datasource_content
22
+ from nautobot.extras.datasources import (
23
+ ensure_git_repository,
24
+ git_repository_dry_run,
25
+ refresh_datasource_content,
26
+ refresh_job_code_from_repository,
27
+ )
23
28
  from nautobot.extras.jobs import BooleanVar, ChoiceVar, FileVar, Job, ObjectVar, RunJobTaskFailed, StringVar, TextVar
24
29
  from nautobot.extras.models import ExportTemplate, GitRepository
25
30
 
@@ -49,13 +54,24 @@ class GitRepositorySync(Job):
49
54
  self.logger.info(f'Creating/refreshing local copy of Git repository "{repository.name}"...')
50
55
 
51
56
  try:
52
- ensure_git_repository(repository, logger=self.logger)
53
- refresh_datasource_content("extras.gitrepository", repository, user, job_result, delete=False)
54
- # Given that the above succeeded, tell all workers (including ourself) to call ensure_git_repository()
55
- app.control.broadcast("refresh_git_repository", repository_pk=repository.pk, head=repository.current_head)
56
- finally:
57
- if job_result.duration:
58
- self.logger.info("Repository synchronization completed in %s", job_result.duration)
57
+ with transaction.atomic():
58
+ ensure_git_repository(repository, logger=self.logger)
59
+ refresh_datasource_content("extras.gitrepository", repository, user, job_result, delete=False)
60
+ # Given that the above succeeded, tell all workers (including ourself) to call ensure_git_repository()
61
+ app.control.broadcast(
62
+ "refresh_git_repository", repository_pk=repository.pk, head=repository.current_head
63
+ )
64
+ if job_result.duration:
65
+ self.logger.info("Repository synchronization completed in %s", job_result.duration)
66
+ except Exception:
67
+ job_result.log("Changes to database records have been reverted.")
68
+ # Re-check-out previous commit if any
69
+ repository.refresh_from_db()
70
+ if repository.current_head:
71
+ job_result.log(f"Attempting to revert local repository clone to commit {repository.current_head}")
72
+ ensure_git_repository(repository, logger=self.logger, head=repository.current_head)
73
+ refresh_job_code_from_repository(repository.slug, ignore_import_errors=True)
74
+ raise
59
75
 
60
76
 
61
77
  class GitRepositoryDryRun(Job):
@@ -1,9 +1,11 @@
1
1
  from django.core.cache import cache
2
2
  from django.db.models import Case, When
3
+ from django.db.models.signals import post_delete, post_save
3
4
  from tree_queries.models import TreeNode
4
5
  from tree_queries.query import TreeManager as TreeManager_, TreeQuerySet as TreeQuerySet_
5
6
 
6
7
  from nautobot.core.models import BaseManager, querysets
8
+ from nautobot.core.signals import invalidate_max_depth_cache
7
9
 
8
10
 
9
11
  class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
@@ -117,3 +119,9 @@ class TreeModel(TreeNode):
117
119
  display_str += self.name
118
120
  cache.set(cache_key, display_str, 5)
119
121
  return display_str
122
+
123
+ @classmethod
124
+ def __init_subclass__(cls, **kwargs):
125
+ super().__init_subclass__(**kwargs)
126
+ post_save.connect(invalidate_max_depth_cache, sender=cls)
127
+ post_delete.connect(invalidate_max_depth_cache, sender=cls)
nautobot/core/settings.py CHANGED
@@ -418,7 +418,14 @@ if "NAUTOBOT_ALLOWED_HOSTS" in os.environ and os.environ["NAUTOBOT_ALLOWED_HOSTS
418
418
  ALLOWED_HOSTS = os.environ["NAUTOBOT_ALLOWED_HOSTS"].split(" ")
419
419
  else:
420
420
  ALLOWED_HOSTS = []
421
+
422
+ # Allow CSRF trusted origins to be set via an environment variable
423
+ # This allows Nautobot to be run behind a reverse proxy that terminates TLS
424
+ # and fix potential issues with CSRF validation
421
425
  CSRF_TRUSTED_ORIGINS = []
426
+ if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
427
+ CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
428
+
422
429
  CSRF_FAILURE_VIEW = "nautobot.core.views.csrf_failure"
423
430
  DATE_FORMAT = os.getenv("NAUTOBOT_DATE_FORMAT", "N j, Y")
424
431
  DATETIME_FORMAT = os.getenv("NAUTOBOT_DATETIME_FORMAT", "N j, Y g:i a")
@@ -587,6 +587,16 @@ properties:
587
587
  description: >-
588
588
  A list of hosts (fully-qualified domain names (FQDNs) or subdomains) that are considered trusted origins
589
589
  for cross-site secure requests such as HTTPS POST.
590
+
591
+ You might need to set this if you are using a reverse proxy or load balancer that changes the host header or if you face the error
592
+ 'Invalid Form Submission - CSRF failure has occured' and 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
593
+
594
+ Example:
595
+
596
+ ```python
597
+ CSRF_TRUSTED_ORIGINS = ['https://subdomain.example.com', 'https://*.example.com']
598
+ ```
599
+ environment_variable: "NAUTOBOT_CSRF_TRUSTED_ORIGINS"
590
600
  items:
591
601
  type: "string"
592
602
  see_also:
nautobot/core/signals.py CHANGED
@@ -7,7 +7,6 @@ import logging
7
7
 
8
8
  from django.contrib.auth.signals import user_logged_in, user_logged_out
9
9
  from django.core.cache import cache
10
- from django.db.models.signals import post_delete, post_save
11
10
  from django.dispatch import receiver, Signal
12
11
  import redis.exceptions
13
12
 
@@ -62,10 +61,12 @@ def disable_for_loaddata(signal_handler):
62
61
  return wrapper
63
62
 
64
63
 
65
- @receiver(post_save)
66
- @receiver(post_delete)
67
64
  def invalidate_max_depth_cache(sender, **kwargs):
68
- """Clear the appropriate TreeManager.max_depth cache as the create/update/delete may have changed the tree."""
65
+ """
66
+ Clear the appropriate TreeManager.max_depth cache as the create/update/delete may have changed the tree.
67
+
68
+ Note that this signal is connected in `TreeModel.__init_subclass__()` so as to only apply to those models.
69
+ """
69
70
  from nautobot.core.models.tree_queries import TreeManager
70
71
 
71
72
  if not isinstance(sender.objects, TreeManager):
@@ -94,9 +94,13 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
94
94
  # FQDNs that are considered trusted origins for secure, cross-domain, requests such as HTTPS POST.
95
95
  # If running Nautobot under a single domain, you may not need to set this variable;
96
96
  # if running on multiple domains, you *may* need to set this variable to more or less the same as ALLOWED_HOSTS above.
97
+ # You also want to set this variable if you are facing CSRF validation issues such as
98
+ # 'CSRF failure has occured' or 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
97
99
  # https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins
98
100
  #
99
101
  # CSRF_TRUSTED_ORIGINS = []
102
+ # if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
103
+ # CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
100
104
 
101
105
  # Date/time formatting. See the following link for supported formats:
102
106
  # https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
nautobot/dcim/forms.py CHANGED
@@ -13,6 +13,8 @@ from nautobot.core.forms import (
13
13
  add_blank_choice,
14
14
  APISelect,
15
15
  APISelectMultiple,
16
+ AutoPositionField,
17
+ AutoPositionPatternField,
16
18
  BootstrapMixin,
17
19
  BulkEditNullBooleanSelect,
18
20
  ColorSelect,
@@ -269,23 +271,7 @@ class ComponentForm(BootstrapMixin, forms.Form):
269
271
 
270
272
 
271
273
  class ModularComponentForm(ComponentForm):
272
- name_pattern = ExpandableNameField(
273
- label="Name",
274
- help_text="""
275
- Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
276
- are not supported. Examples:
277
- <ul>
278
- <li><code>[ge,xe]-0/0/[0-9]</code></li>
279
- <li><code>e[0-3][a-d,f]</code></li>
280
- </ul>
281
-
282
- The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
283
- may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
284
- module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
285
- multiple times in the component name and there is no limit to the depth of parent levels.
286
- Any variables that cannot be replaced by a suitable position value will remain unchanged.
287
- """,
288
- )
274
+ """Base class for forms for components that can be assigned to either a device or a module."""
289
275
 
290
276
 
291
277
  #
@@ -1064,11 +1050,27 @@ class ComponentTemplateCreateForm(ComponentForm):
1064
1050
  description = forms.CharField(required=False)
1065
1051
 
1066
1052
 
1067
- class ModularComponentTemplateCreateForm(ModularComponentForm):
1053
+ class ModularComponentTemplateCreateForm(ComponentTemplateCreateForm):
1068
1054
  """
1069
1055
  Base form for the creation of modular device component templates (subclassed from ModularComponentTemplateModel).
1070
1056
  """
1071
1057
 
1058
+ name_pattern = ExpandableNameField(
1059
+ label="Name",
1060
+ help_text="""
1061
+ Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
1062
+ are not supported. Examples:
1063
+ <ul>
1064
+ <li><code>[ge,xe]-0/0/[0-9]</code></li>
1065
+ <li><code>e[0-3][a-d,f]</code></li>
1066
+ </ul>
1067
+
1068
+ The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
1069
+ may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
1070
+ module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
1071
+ multiple times in the component name and there is no limit to the depth of parent levels.
1072
+ Any variables that cannot be replaced by a suitable position value will remain unchanged.""",
1073
+ )
1072
1074
  device_type = DynamicModelChoiceField(
1073
1075
  queryset=DeviceType.objects.all(),
1074
1076
  required=False,
@@ -1077,7 +1079,6 @@ class ModularComponentTemplateCreateForm(ModularComponentForm):
1077
1079
  queryset=ModuleType.objects.all(),
1078
1080
  required=False,
1079
1081
  )
1080
- description = forms.CharField(required=False)
1081
1082
 
1082
1083
 
1083
1084
  class ConsolePortTemplateForm(ModularComponentTemplateForm):
@@ -1570,10 +1571,10 @@ class ModuleBayBaseCreateForm(BootstrapMixin, forms.Form):
1570
1571
  required=False,
1571
1572
  help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
1572
1573
  )
1573
- position_pattern = ExpandableNameField(
1574
- label="Position",
1574
+ position_pattern = AutoPositionPatternField(
1575
1575
  required=False,
1576
- help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
1576
+ help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)"
1577
+ " Default to the names of the module bays unless manually supplied by the user.",
1577
1578
  )
1578
1579
  description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
1579
1580
 
@@ -2542,12 +2543,8 @@ class DeviceBulkAddComponentForm(ComponentForm, CustomFieldModelBulkEditFormMixi
2542
2543
  nullable_fields = []
2543
2544
 
2544
2545
 
2545
- class ModuleBulkAddComponentForm(ModularComponentForm, CustomFieldModelBulkEditFormMixin):
2546
+ class ModuleBulkAddComponentForm(DeviceBulkAddComponentForm):
2546
2547
  pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
2547
- description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
2548
-
2549
- class Meta:
2550
- nullable_fields = []
2551
2548
 
2552
2549
 
2553
2550
  #
@@ -3566,6 +3563,12 @@ class ModuleBayFilterForm(NautobotFilterForm):
3566
3563
 
3567
3564
 
3568
3565
  class ModuleBayForm(NautobotModelForm):
3566
+ position = AutoPositionField(
3567
+ max_length=CHARFIELD_MAX_LENGTH,
3568
+ help_text="The position of the module bay within the parent device/module. "
3569
+ "Defaults to the name of the module bay unless overridden.",
3570
+ required=False,
3571
+ )
3569
3572
  parent_device = DynamicModelChoiceField(
3570
3573
  queryset=Device.objects.all(),
3571
3574
  required=False,
@@ -1280,3 +1280,8 @@ class ModuleBay(PrimaryModel):
1280
1280
 
1281
1281
  if not (self.parent_device or self.parent_module):
1282
1282
  raise ValidationError("Either parent_device or parent_module must be set")
1283
+
1284
+ # Populate the position field with the name of the module bay if it is not supplied by the user.
1285
+
1286
+ if not self.position:
1287
+ self.position = self.name
@@ -173,6 +173,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
173
173
  template_code="""{% if record.device_redundancy_group %}<span class="badge badge-default">{{ record.device_redundancy_group_priority|default:'None' }}</span>{% else %}—{% endif %}"""
174
174
  )
175
175
  controller_managed_device_group = tables.Column(linkify=True)
176
+ software_version = tables.Column(linkify=True, verbose_name="Software Version")
176
177
  secrets_group = tables.Column(linkify=True)
177
178
  tags = TagColumn(url_name="dcim:device_list")
178
179
 
@@ -201,6 +202,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
201
202
  "vc_priority",
202
203
  "device_redundancy_group",
203
204
  "device_redundancy_group_priority",
205
+ "software_version",
204
206
  "controller_managed_device_group",
205
207
  "secrets_group",
206
208
  "tags",
@@ -616,7 +618,7 @@ class DeviceModulePowerOutletTable(PowerOutletTable):
616
618
  row_attrs = {"class": cable_status_color_css}
617
619
 
618
620
 
619
- class BaseInterfaceTable(BaseTable):
621
+ class BaseInterfaceTable(StatusTableMixin, RoleTableMixin, BaseTable):
620
622
  enabled = BooleanColumn()
621
623
  ip_addresses = tables.TemplateColumn(
622
624
  template_code=INTERFACE_IPADDRESSES,
@@ -632,7 +634,7 @@ class BaseInterfaceTable(BaseTable):
632
634
  vrf = tables.Column(linkify=True, verbose_name="VRF")
633
635
 
634
636
 
635
- class InterfaceTable(StatusTableMixin, ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
637
+ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
636
638
  mgmt_only = BooleanColumn()
637
639
  tags = TagColumn(url_name="dcim:interface_list")
638
640
 
@@ -2849,6 +2849,22 @@ class ModuleBayTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
2849
2849
  self.assertIsNone(child_module_bay.parent)
2850
2850
  self.assertIsNone(grandchild_module_bay.parent)
2851
2851
 
2852
+ def test_position_value_auto_population(self):
2853
+ """
2854
+ Assert that the value of the module bay position is auto-populated by its name if position is not provided by the user.
2855
+ """
2856
+
2857
+ module_bay = ModuleBay.objects.create(
2858
+ parent_device=self.device,
2859
+ name="1111",
2860
+ )
2861
+ module_bay.validated_save()
2862
+ self.assertEqual(module_bay.position, module_bay.name)
2863
+ # Test the default value is overriden if the user provides a position value.
2864
+ module_bay.position = "1222"
2865
+ module_bay.validated_save()
2866
+ self.assertEqual(module_bay.position, "1222")
2867
+
2852
2868
 
2853
2869
  class ModuleBayTemplateTestCase(ModularDeviceComponentTemplateTestCaseMixin, ModelTestCases.BaseModelTestCase):
2854
2870
  model = ModuleBayTemplate
@@ -180,7 +180,7 @@ def web_request_context(
180
180
  Valid choices are in `nautobot.extras.choices.ObjectChangeEventContextChoices`.
181
181
  :param request: Optional web request instance, one will be generated if not supplied
182
182
  """
183
- from nautobot.extras.jobs import enqueue_job_hooks, get_jobs # prevent circular import
183
+ from nautobot.extras.jobs import enqueue_job_hooks # prevent circular import
184
184
 
185
185
  valid_contexts = {
186
186
  ObjectChangeEventContextChoices.CONTEXT_JOB: JobChangeContext,
@@ -203,15 +203,14 @@ def web_request_context(
203
203
  with change_logging(change_context):
204
204
  yield request
205
205
  finally:
206
- jobs_refreshed = False
206
+ jobs_reloaded = False
207
207
  # enqueue jobhooks and webhooks, use change_context.change_id in case change_id was not supplied
208
- for object_change in ObjectChange.objects.filter(request_id=change_context.change_id).iterator():
208
+ for object_change in (
209
+ ObjectChange.objects.filter(request_id=change_context.change_id).order_by("time").iterator()
210
+ ):
209
211
  if context != ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK:
210
- # Make sure JobHooks are up to date (once) before calling them
211
- if not jobs_refreshed:
212
- get_jobs(reload=True)
213
- jobs_refreshed = True
214
- enqueue_job_hooks(object_change)
212
+ # Make sure JobHooks are up to date (only once) before calling them
213
+ jobs_reloaded |= enqueue_job_hooks(object_change, may_reload_jobs=(not jobs_reloaded))
215
214
  enqueue_webhooks(object_change)
216
215
 
217
216
 
@@ -4,6 +4,7 @@ from .git import (
4
4
  ensure_git_repository,
5
5
  get_repo_access_url,
6
6
  git_repository_dry_run,
7
+ refresh_job_code_from_repository,
7
8
  )
8
9
  from .registry import (
9
10
  get_datasource_content_choices,
@@ -20,4 +21,5 @@ __all__ = (
20
21
  "get_repo_access_url",
21
22
  "git_repository_dry_run",
22
23
  "refresh_datasource_content",
24
+ "refresh_job_code_from_repository",
23
25
  )
@@ -96,8 +96,11 @@ def get_repo_access_url(repository_record):
96
96
  obj=repository_record,
97
97
  )
98
98
  except ObjectDoesNotExist:
99
- # No defined secret, fall through to legacy behavior
100
- pass
99
+ logger.warning(
100
+ "HTTP Token not found for secrets group %s associated with repository %s",
101
+ repository_record.secrets_group,
102
+ repository_record,
103
+ )
101
104
  try:
102
105
  user = repository_record.secrets_group.get_secret_value(
103
106
  SecretsGroupAccessTypeChoices.TYPE_HTTP,
@@ -105,8 +108,12 @@ def get_repo_access_url(repository_record):
105
108
  obj=repository_record,
106
109
  )
107
110
  except ObjectDoesNotExist:
108
- # No defined secret, fall through to legacy behavior
109
- pass
111
+ # May not be needed for this repository, so just log as debug rather than warning
112
+ logger.debug(
113
+ "HTTP Username not found for secrets group %s associated with repository %s",
114
+ repository_record.secrets_group,
115
+ repository_record,
116
+ )
110
117
 
111
118
  if token and token not in from_url:
112
119
  # Some git repositories require a user as well as a token.
@@ -147,7 +154,7 @@ def ensure_git_repository(repository_record, logger=None, head=None): # pylint:
147
154
  (bool): Whether any change to the local repo actually occurred.
148
155
  """
149
156
  # We want to check if the repo is already checked out at head. We also want to avoid calling
150
- # get_repo_from_utl_to_path_and_from_branch, because it will cause the URL to be rebuilt causing calls to a secrets
157
+ # get_repo_from_url_to_path_and_from_branch, because it will cause the URL to be rebuilt causing calls to a secrets
151
158
  # backend. As such, if head is None, we can't perform these checks.
152
159
  if head is not None:
153
160
  # If the repo exists and has HEAD already checked out, the repo is present and has the correct branch selected.
@@ -205,7 +212,6 @@ def git_repository_dry_run(repository_record, logger): # pylint: disable=redefi
205
212
  logger.info("%s - `%s`", item.status, item.text)
206
213
  else:
207
214
  logger.info("Repository has no changes")
208
-
209
215
  except Exception as exc:
210
216
  logger.error(str(exc))
211
217
  raise
@@ -435,19 +441,12 @@ def import_config_context(context_data, repository_record, job_result):
435
441
  created = False
436
442
  modified = False
437
443
  save_needed = False
438
- try:
439
- context_record = ConfigContext.objects.get(
440
- name=context_metadata.get("name"),
441
- owner_content_type=git_repository_content_type,
442
- owner_object_id=repository_record.pk,
443
- )
444
- except ConfigContext.DoesNotExist:
445
- context_record = ConfigContext(
446
- name=context_metadata.get("name"),
447
- owner_content_type=git_repository_content_type,
448
- owner_object_id=repository_record.pk,
449
- )
450
- created = True
444
+ context_record, created = ConfigContext.objects.get_or_create(
445
+ name=context_metadata.get("name"),
446
+ owner_content_type=git_repository_content_type,
447
+ owner_object_id=repository_record.pk,
448
+ defaults={"data": {}},
449
+ )
451
450
 
452
451
  for field in ("weight", "description", "is_active"):
453
452
  new_value = context_metadata[field]
@@ -662,20 +661,12 @@ def import_config_context_schema(context_schema_data, repository_record, job_res
662
661
 
663
662
  schema_metadata = context_schema_data["_metadata"]
664
663
 
665
- try:
666
- schema_record = ConfigContextSchema.objects.get(
667
- name=schema_metadata["name"],
668
- owner_content_type=git_repository_content_type,
669
- owner_object_id=repository_record.pk,
670
- )
671
- except ConfigContextSchema.DoesNotExist:
672
- schema_record = ConfigContextSchema(
673
- name=schema_metadata["name"],
674
- owner_content_type=git_repository_content_type,
675
- owner_object_id=repository_record.pk,
676
- data_schema=context_schema_data["data_schema"],
677
- )
678
- created = True
664
+ schema_record, created = ConfigContextSchema.objects.get_or_create(
665
+ name=schema_metadata["name"],
666
+ owner_content_type=git_repository_content_type,
667
+ owner_object_id=repository_record.pk,
668
+ defaults={"data_schema": context_schema_data["data_schema"]},
669
+ )
679
670
 
680
671
  if schema_record.description != schema_metadata.get("description", ""):
681
672
  schema_record.description = schema_metadata.get("description", "")
@@ -868,22 +859,12 @@ def update_git_export_templates(repository_record, job_result):
868
859
  # To reduce noise until the base issue is fixed, we need to explicitly detect object changes:
869
860
  created = False
870
861
  modified = False
871
- try:
872
- template_record = ExportTemplate.objects.get(
873
- content_type=model_content_type,
874
- name=file_name,
875
- owner_content_type=git_repository_content_type,
876
- owner_object_id=repository_record.pk,
877
- )
878
- except ExportTemplate.DoesNotExist:
879
- template_record = ExportTemplate(
880
- content_type=model_content_type,
881
- name=file_name,
882
- owner_content_type=git_repository_content_type,
883
- owner_object_id=repository_record.pk,
884
- )
885
- created = True
886
- modified = True
862
+ template_record, created = ExportTemplate.objects.get_or_create(
863
+ content_type=model_content_type,
864
+ name=file_name,
865
+ owner_content_type=git_repository_content_type,
866
+ owner_object_id=repository_record.pk,
867
+ )
887
868
 
888
869
  if template_record.template_code != template_content:
889
870
  template_record.template_code = template_content
@@ -32,7 +32,7 @@ def refresh_datasource_content(model_name, record, user, job_result, delete=Fals
32
32
  job_result (JobResult): Passed through to the callback functions to use with logging their actions.
33
33
  delete (bool): True if the record is being deleted; False if it is being created/updated.
34
34
  """
35
- job_result.log(f"Refreshing data provided by {record}...", level_choice=LogLevelChoices.LOG_INFO)
35
+ job_result.log(f"Refreshing data provided by {record}...", level_choice=LogLevelChoices.LOG_INFO, obj=record)
36
36
 
37
37
  for entry in get_datasource_contents(model_name):
38
38
  job_result.log(f"Refreshing {entry.name}...", level_choice=LogLevelChoices.LOG_INFO)
@@ -54,4 +54,4 @@ def refresh_datasource_content(model_name, record, user, job_result, delete=Fals
54
54
  raise RuntimeError(msg)
55
55
 
56
56
  # Otherwise, log a friendly info message.
57
- job_result.log(f"Data refresh from {record} complete!", level_choice=LogLevelChoices.LOG_INFO)
57
+ job_result.log(f"Data refresh from {record} complete!", level_choice=LogLevelChoices.LOG_INFO, obj=record)
nautobot/extras/jobs.py CHANGED
@@ -1144,20 +1144,23 @@ def run_job(self, job_class_path, *args, **kwargs):
1144
1144
  raise
1145
1145
 
1146
1146
 
1147
- def enqueue_job_hooks(object_change):
1147
+ def enqueue_job_hooks(object_change, may_reload_jobs=True):
1148
1148
  """
1149
- Find job hook(s) assigned to this changed object type + action and enqueue them
1150
- to be processed
1149
+ Find job hook(s) assigned to this changed object type + action and enqueue them to be processed.
1150
+
1151
+ Returns:
1152
+ jobs_reloaded (bool): whether Jobs were reloaded to make this happen
1151
1153
  """
1154
+ jobs_reloaded = False
1152
1155
 
1153
1156
  # Job hooks cannot trigger other job hooks
1154
1157
  if object_change.change_context == ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK:
1155
- return
1158
+ return jobs_reloaded
1156
1159
 
1157
1160
  # Determine whether this type of object supports job hooks
1158
1161
  content_type = object_change.changed_object_type
1159
1162
  if content_type not in change_logged_models_queryset():
1160
- return
1163
+ return jobs_reloaded
1161
1164
 
1162
1165
  # Retrieve any applicable job hooks
1163
1166
  action_flag = {
@@ -1167,7 +1170,14 @@ def enqueue_job_hooks(object_change):
1167
1170
  }[object_change.action]
1168
1171
  job_hooks = JobHook.objects.filter(content_types=content_type, enabled=True, **{action_flag: True})
1169
1172
 
1173
+ if not job_hooks.exists():
1174
+ return jobs_reloaded
1175
+
1170
1176
  # Enqueue the jobs related to the job_hooks
1177
+ if may_reload_jobs:
1178
+ get_jobs(reload=True)
1179
+ jobs_reloaded = True
1180
+
1171
1181
  for job_hook in job_hooks:
1172
1182
  job_model = job_hook.job
1173
1183
  if not job_model.installed or not job_model.enabled:
@@ -1178,3 +1188,5 @@ def enqueue_job_hooks(object_change):
1178
1188
  logger.error("JobHook %s is enabled, but the underlying Job implementation is missing", job_hook)
1179
1189
  else:
1180
1190
  JobResult.enqueue_job(job_model, object_change.user, object_change=object_change.pk)
1191
+
1192
+ return jobs_reloaded
@@ -115,6 +115,12 @@ class GitRepository(PrimaryModel):
115
115
  "provides contents overlapping with this repository."
116
116
  )
117
117
 
118
+ # Changing branch or remote_url invalidates current_head
119
+ if self.present_in_database:
120
+ past = GitRepository.objects.get(id=self.id)
121
+ if self.remote_url != past.remote_url or self.branch != past.branch:
122
+ self.current_head = ""
123
+
118
124
  def get_latest_sync(self):
119
125
  """
120
126
  Return a `JobResult` for the latest sync operation.