nautobot 2.4.9__py3-none-any.whl → 2.4.11__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 (433) hide show
  1. nautobot/cloud/tests/test_views.py +13 -1
  2. nautobot/cloud/views.py +39 -9
  3. nautobot/core/api/parsers.py +56 -2
  4. nautobot/core/celery/__init__.py +21 -0
  5. nautobot/core/celery/encoders.py +3 -0
  6. nautobot/core/forms/forms.py +4 -1
  7. nautobot/core/jobs/bulk_actions.py +8 -8
  8. nautobot/core/jobs/cleanup.py +11 -0
  9. nautobot/core/management/commands/generate_test_data.py +2 -1
  10. nautobot/core/models/__init__.py +2 -0
  11. nautobot/core/templates/generic/object_retrieve.html +1 -1
  12. nautobot/core/testing/mixins.py +19 -1
  13. nautobot/core/testing/views.py +104 -8
  14. nautobot/core/tests/test_csv.py +92 -1
  15. nautobot/core/tests/test_jinja_filters.py +59 -0
  16. nautobot/core/tests/test_jobs.py +20 -4
  17. nautobot/core/tests/test_utils.py +193 -0
  18. nautobot/core/tests/test_views.py +73 -0
  19. nautobot/core/tests/test_views_utils.py +53 -2
  20. nautobot/core/ui/object_detail.py +4 -0
  21. nautobot/core/urls.py +2 -2
  22. nautobot/core/utils/lookup.py +4 -2
  23. nautobot/core/utils/module_loading.py +86 -58
  24. nautobot/core/views/__init__.py +21 -0
  25. nautobot/core/views/generic.py +2 -12
  26. nautobot/core/views/mixins.py +19 -1
  27. nautobot/core/views/renderers.py +4 -13
  28. nautobot/core/views/utils.py +16 -0
  29. nautobot/dcim/api/serializers.py +13 -0
  30. nautobot/dcim/api/urls.py +1 -0
  31. nautobot/dcim/api/views.py +20 -0
  32. nautobot/dcim/apps.py +1 -0
  33. nautobot/dcim/factory.py +11 -0
  34. nautobot/dcim/filters/__init__.py +110 -0
  35. nautobot/dcim/forms.py +205 -19
  36. nautobot/dcim/migrations/0070_modulefamily_models.py +92 -0
  37. nautobot/dcim/models/__init__.py +2 -0
  38. nautobot/dcim/models/device_component_templates.py +18 -0
  39. nautobot/dcim/models/device_components.py +25 -1
  40. nautobot/dcim/models/devices.py +68 -0
  41. nautobot/dcim/navigation.py +16 -0
  42. nautobot/dcim/tables/__init__.py +2 -0
  43. nautobot/dcim/tables/devices.py +48 -0
  44. nautobot/dcim/tables/devicetypes.py +35 -1
  45. nautobot/dcim/tables/template_code.py +2 -0
  46. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -90
  47. nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
  48. nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +1 -63
  49. nautobot/dcim/templates/dcim/location.html +2 -249
  50. nautobot/dcim/templates/dcim/location_edit.html +2 -38
  51. nautobot/dcim/templates/dcim/location_retrieve.html +249 -0
  52. nautobot/dcim/templates/dcim/location_update.html +38 -0
  53. nautobot/dcim/templates/dcim/module_update.html +1 -0
  54. nautobot/dcim/templates/dcim/modulebay_retrieve.html +93 -1
  55. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +31 -0
  56. nautobot/dcim/templates/dcim/moduletype_retrieve.html +6 -0
  57. nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -160
  58. nautobot/dcim/tests/test_api.py +35 -0
  59. nautobot/dcim/tests/test_filters.py +102 -3
  60. nautobot/dcim/tests/test_models.py +146 -0
  61. nautobot/dcim/tests/test_views.py +70 -97
  62. nautobot/dcim/urls.py +4 -22
  63. nautobot/dcim/views.py +439 -153
  64. nautobot/extras/api/views.py +9 -2
  65. nautobot/extras/context_managers.py +2 -2
  66. nautobot/extras/datasources/git.py +11 -3
  67. nautobot/extras/forms/forms.py +9 -5
  68. nautobot/extras/jobs.py +4 -2
  69. nautobot/extras/models/customfields.py +2 -0
  70. nautobot/extras/models/datasources.py +13 -8
  71. nautobot/extras/models/groups.py +18 -0
  72. nautobot/extras/models/jobs.py +19 -0
  73. nautobot/extras/models/metadata.py +2 -0
  74. nautobot/extras/models/models.py +4 -0
  75. nautobot/extras/models/secrets.py +7 -0
  76. nautobot/extras/plugins/__init__.py +3 -0
  77. nautobot/extras/secrets/__init__.py +14 -0
  78. nautobot/extras/tables.py +40 -3
  79. nautobot/extras/templates/extras/configcontext.html +2 -220
  80. nautobot/extras/templates/extras/configcontext_edit.html +2 -50
  81. nautobot/extras/templates/extras/configcontext_retrieve.html +2 -0
  82. nautobot/extras/templates/extras/configcontext_update.html +50 -0
  83. nautobot/extras/templates/extras/configcontextschema.html +2 -48
  84. nautobot/extras/templates/extras/configcontextschema_edit.html +2 -19
  85. nautobot/extras/templates/extras/configcontextschema_retrieve.html +48 -0
  86. nautobot/extras/templates/extras/configcontextschema_update.html +19 -0
  87. nautobot/extras/templates/extras/inc/configcontext_data.html +1 -0
  88. nautobot/extras/templates/extras/inc/json_data.html +1 -1
  89. nautobot/extras/templates/extras/inc/json_format.html +2 -2
  90. nautobot/extras/templates/extras/job_edit.html +12 -6
  91. nautobot/extras/templates/extras/tag.html +2 -52
  92. nautobot/extras/templates/extras/tag_edit.html +2 -15
  93. nautobot/extras/templates/extras/tag_retrieve.html +52 -0
  94. nautobot/extras/templates/extras/tag_update.html +15 -0
  95. nautobot/extras/templates/extras/team_retrieve.html +2 -2
  96. nautobot/extras/tests/test_api.py +15 -15
  97. nautobot/extras/tests/test_context_managers.py +20 -0
  98. nautobot/extras/tests/test_filters.py +4 -4
  99. nautobot/extras/tests/test_jobs.py +23 -10
  100. nautobot/extras/tests/test_models.py +45 -8
  101. nautobot/extras/tests/test_plugins.py +6 -3
  102. nautobot/extras/tests/test_views.py +66 -11
  103. nautobot/extras/urls.py +4 -134
  104. nautobot/extras/views.py +113 -158
  105. nautobot/ipam/models.py +51 -4
  106. nautobot/ipam/tables.py +19 -0
  107. nautobot/ipam/templates/ipam/vlan.html +2 -84
  108. nautobot/ipam/templates/ipam/vlan_edit.html +2 -24
  109. nautobot/ipam/templates/ipam/vlan_retrieve.html +84 -0
  110. nautobot/ipam/templates/ipam/vlan_update.html +24 -0
  111. nautobot/ipam/tests/test_views.py +5 -0
  112. nautobot/ipam/urls.py +1 -21
  113. nautobot/ipam/views.py +45 -70
  114. nautobot/project-static/docs/404.html +31 -8
  115. nautobot/project-static/docs/apps/index.html +31 -8
  116. nautobot/project-static/docs/apps/nautobot-apps.html +31 -8
  117. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +31 -8
  118. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +31 -8
  119. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +31 -8
  120. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +31 -8
  121. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +31 -8
  122. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +31 -8
  123. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +31 -8
  124. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +31 -8
  125. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +31 -8
  126. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +31 -8
  127. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +31 -8
  128. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +31 -8
  129. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +31 -8
  130. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +31 -8
  131. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +31 -8
  132. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +31 -8
  133. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +31 -8
  134. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +31 -8
  135. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +31 -8
  136. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +120 -8
  137. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +31 -8
  138. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +31 -8
  139. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +31 -8
  140. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +31 -8
  141. nautobot/project-static/docs/development/apps/api/configuration-view.html +31 -8
  142. nautobot/project-static/docs/development/apps/api/database-backend-config.html +31 -8
  143. nautobot/project-static/docs/development/apps/api/models/django-admin.html +31 -8
  144. nautobot/project-static/docs/development/apps/api/models/global-search.html +31 -8
  145. nautobot/project-static/docs/development/apps/api/models/graphql.html +31 -8
  146. nautobot/project-static/docs/development/apps/api/models/index.html +31 -8
  147. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +40 -8
  148. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +31 -8
  149. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +31 -8
  150. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +31 -8
  151. nautobot/project-static/docs/development/apps/api/platform-features/index.html +31 -8
  152. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +31 -8
  153. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +31 -8
  154. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +31 -8
  155. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +70 -46
  156. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +31 -8
  157. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +31 -8
  158. nautobot/project-static/docs/development/apps/api/prometheus.html +31 -8
  159. nautobot/project-static/docs/development/apps/api/setup.html +31 -8
  160. nautobot/project-static/docs/development/apps/api/testing.html +31 -8
  161. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +31 -8
  162. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +31 -8
  163. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +31 -8
  164. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +31 -8
  165. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +31 -8
  166. nautobot/project-static/docs/development/apps/api/views/base-template.html +31 -8
  167. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +31 -8
  168. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +31 -8
  169. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +31 -8
  170. nautobot/project-static/docs/development/apps/api/views/index.html +31 -8
  171. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +31 -8
  172. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -8
  173. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +31 -8
  174. nautobot/project-static/docs/development/apps/api/views/notes.html +31 -8
  175. nautobot/project-static/docs/development/apps/api/views/rest-api.html +31 -8
  176. nautobot/project-static/docs/development/apps/api/views/urls.html +31 -8
  177. nautobot/project-static/docs/development/apps/index.html +31 -8
  178. nautobot/project-static/docs/development/apps/migration/code-updates.html +31 -8
  179. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +31 -8
  180. nautobot/project-static/docs/development/apps/migration/from-v1.html +31 -8
  181. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +31 -8
  182. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +31 -8
  183. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +31 -8
  184. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +31 -8
  185. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +31 -8
  186. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +31 -8
  187. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +31 -8
  188. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +31 -8
  189. nautobot/project-static/docs/development/apps/porting-from-netbox.html +31 -8
  190. nautobot/project-static/docs/development/core/application-registry.html +31 -8
  191. nautobot/project-static/docs/development/core/best-practices.html +31 -8
  192. nautobot/project-static/docs/development/core/bootstrap-ui.html +31 -8
  193. nautobot/project-static/docs/development/core/caching.html +31 -8
  194. nautobot/project-static/docs/development/core/controllers.html +31 -8
  195. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +31 -8
  196. nautobot/project-static/docs/development/core/generic-views.html +31 -8
  197. nautobot/project-static/docs/development/core/getting-started.html +31 -8
  198. nautobot/project-static/docs/development/core/homepage.html +31 -8
  199. nautobot/project-static/docs/development/core/index.html +31 -8
  200. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +31 -8
  201. nautobot/project-static/docs/development/core/model-checklist.html +31 -8
  202. nautobot/project-static/docs/development/core/model-features.html +31 -8
  203. nautobot/project-static/docs/development/core/natural-keys.html +31 -8
  204. nautobot/project-static/docs/development/core/navigation-menu.html +31 -8
  205. nautobot/project-static/docs/development/core/release-checklist.html +31 -8
  206. nautobot/project-static/docs/development/core/role-internals.html +31 -8
  207. nautobot/project-static/docs/development/core/settings.html +31 -8
  208. nautobot/project-static/docs/development/core/style-guide.html +31 -8
  209. nautobot/project-static/docs/development/core/templates.html +31 -8
  210. nautobot/project-static/docs/development/core/testing.html +31 -8
  211. nautobot/project-static/docs/development/core/ui-component-framework.html +31 -8
  212. nautobot/project-static/docs/development/core/user-preferences.html +31 -8
  213. nautobot/project-static/docs/development/index.html +31 -8
  214. nautobot/project-static/docs/development/jobs/getting-started.html +35 -8
  215. nautobot/project-static/docs/development/jobs/index.html +31 -8
  216. nautobot/project-static/docs/development/jobs/installation.html +31 -8
  217. nautobot/project-static/docs/development/jobs/job-extensions.html +31 -8
  218. nautobot/project-static/docs/development/jobs/job-logging.html +31 -8
  219. nautobot/project-static/docs/development/jobs/job-patterns.html +31 -8
  220. nautobot/project-static/docs/development/jobs/job-structure.html +31 -8
  221. nautobot/project-static/docs/development/jobs/migration/from-v1.html +31 -8
  222. nautobot/project-static/docs/development/jobs/testing.html +31 -8
  223. nautobot/project-static/docs/index.html +31 -8
  224. nautobot/project-static/docs/insert-analytics.sh +36 -0
  225. nautobot/project-static/docs/objects.inv +0 -0
  226. nautobot/project-static/docs/overview/application_stack.html +31 -8
  227. nautobot/project-static/docs/overview/design_philosophy.html +31 -8
  228. nautobot/project-static/docs/release-notes/index.html +31 -8
  229. nautobot/project-static/docs/release-notes/version-1.0.html +31 -8
  230. nautobot/project-static/docs/release-notes/version-1.1.html +31 -8
  231. nautobot/project-static/docs/release-notes/version-1.2.html +31 -8
  232. nautobot/project-static/docs/release-notes/version-1.3.html +31 -8
  233. nautobot/project-static/docs/release-notes/version-1.4.html +31 -8
  234. nautobot/project-static/docs/release-notes/version-1.5.html +31 -8
  235. nautobot/project-static/docs/release-notes/version-1.6.html +328 -8
  236. nautobot/project-static/docs/release-notes/version-2.0.html +31 -8
  237. nautobot/project-static/docs/release-notes/version-2.1.html +31 -8
  238. nautobot/project-static/docs/release-notes/version-2.2.html +31 -8
  239. nautobot/project-static/docs/release-notes/version-2.3.html +31 -8
  240. nautobot/project-static/docs/release-notes/version-2.4.html +353 -8
  241. nautobot/project-static/docs/search/search_index.json +1 -1
  242. nautobot/project-static/docs/sitemap.xml +302 -298
  243. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  244. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +31 -8
  245. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +31 -8
  246. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +31 -8
  247. nautobot/project-static/docs/user-guide/administration/configuration/index.html +31 -8
  248. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +31 -8
  249. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +31 -8
  250. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +31 -8
  251. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +31 -8
  252. nautobot/project-static/docs/user-guide/administration/guides/docker.html +31 -8
  253. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +31 -8
  254. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +31 -8
  255. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +31 -8
  256. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +31 -8
  257. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +31 -8
  258. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +31 -8
  259. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +31 -8
  260. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +31 -8
  261. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +31 -8
  262. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +31 -8
  263. nautobot/project-static/docs/user-guide/administration/installation/index.html +31 -8
  264. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +31 -8
  265. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +31 -8
  266. nautobot/project-static/docs/user-guide/administration/installation/services.html +31 -8
  267. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +31 -8
  268. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +31 -8
  269. nautobot/project-static/docs/user-guide/administration/security/index.html +31 -9
  270. nautobot/project-static/docs/user-guide/administration/security/notices.html +144 -9
  271. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +31 -8
  272. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +31 -8
  273. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +31 -8
  274. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +31 -8
  275. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +31 -8
  276. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +31 -8
  277. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +31 -8
  278. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +31 -8
  279. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +31 -8
  280. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +31 -8
  281. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +31 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +31 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +31 -8
  284. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +31 -8
  285. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +31 -8
  286. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +31 -8
  287. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +31 -8
  288. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +31 -8
  289. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +31 -8
  290. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +31 -8
  291. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +31 -8
  292. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +31 -8
  293. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +31 -8
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +31 -8
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +31 -8
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +31 -8
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +31 -8
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +31 -8
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +31 -8
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +31 -8
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +31 -8
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +31 -8
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +31 -8
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +31 -8
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +43 -20
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +31 -8
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +31 -8
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +31 -8
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +31 -8
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +31 -8
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +31 -8
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +31 -8
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +31 -8
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +31 -8
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +31 -8
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +35 -8
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +35 -8
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +35 -8
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +10261 -0
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -11
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +31 -8
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +31 -8
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +31 -8
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +31 -8
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +31 -8
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +31 -8
  327. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +31 -8
  328. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +31 -8
  329. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +31 -8
  330. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +31 -8
  331. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +31 -8
  332. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +31 -8
  333. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +31 -8
  334. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +31 -8
  335. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +31 -8
  336. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +31 -8
  337. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +31 -8
  338. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +31 -8
  339. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +31 -8
  340. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +31 -8
  341. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +31 -8
  342. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +31 -8
  343. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +31 -8
  344. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +31 -8
  345. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +31 -8
  346. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +31 -8
  347. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +31 -8
  348. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +31 -8
  349. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +31 -8
  350. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +31 -8
  351. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +31 -8
  352. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +31 -8
  353. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +31 -8
  354. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +31 -8
  355. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +31 -8
  356. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +31 -8
  357. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +31 -8
  358. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +31 -8
  359. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +31 -8
  360. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +31 -8
  361. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +31 -8
  362. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +31 -8
  363. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +31 -8
  364. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +31 -8
  365. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +31 -8
  366. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +31 -8
  367. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +31 -8
  368. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +31 -8
  369. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +31 -8
  370. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +31 -8
  371. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +31 -8
  372. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +31 -8
  373. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +41 -15
  374. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +31 -8
  375. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +31 -8
  376. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +31 -8
  377. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +31 -8
  378. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +31 -8
  379. nautobot/project-static/docs/user-guide/index.html +31 -8
  380. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +31 -8
  381. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +31 -8
  382. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +31 -8
  383. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +31 -8
  384. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +31 -8
  385. nautobot/project-static/docs/user-guide/platform-functionality/events.html +31 -8
  386. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +31 -8
  387. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +31 -8
  388. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +37 -9
  389. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +31 -8
  390. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +31 -8
  391. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +31 -8
  392. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +31 -8
  393. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +31 -8
  394. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +31 -8
  395. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +31 -8
  396. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +31 -8
  397. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +31 -8
  398. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +31 -8
  399. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +31 -8
  400. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +31 -8
  401. nautobot/project-static/docs/user-guide/platform-functionality/note.html +31 -8
  402. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +31 -8
  403. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +31 -8
  404. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +31 -8
  405. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +31 -8
  406. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +31 -8
  407. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +31 -8
  408. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +31 -8
  409. nautobot/project-static/docs/user-guide/platform-functionality/role.html +31 -8
  410. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +31 -8
  411. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +31 -8
  412. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +31 -8
  413. nautobot/project-static/docs/user-guide/platform-functionality/status.html +31 -8
  414. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +31 -8
  415. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +31 -8
  416. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +31 -8
  417. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +31 -8
  418. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +31 -8
  419. nautobot/tenancy/tables.py +2 -0
  420. nautobot/users/models.py +4 -0
  421. nautobot/virtualization/models.py +4 -0
  422. nautobot/virtualization/tests/test_views.py +1 -1
  423. nautobot/wireless/forms.py +0 -1
  424. nautobot/wireless/models.py +1 -1
  425. nautobot/wireless/tables.py +7 -0
  426. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/METADATA +4 -4
  427. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/RECORD +433 -418
  428. /nautobot/dcim/templates/dcim/{platform_edit.html → platform_create.html} +0 -0
  429. /nautobot/extras/test_jobs/{pass.py → pass_job.py} +0 -0
  430. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/LICENSE.txt +0 -0
  431. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/NOTICE +0 -0
  432. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/WHEEL +0 -0
  433. {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/entry_points.txt +0 -0
@@ -512,12 +512,19 @@ class JobViewSetBase(
512
512
  serializer_class = serializers.JobSerializer
513
513
  filterset_class = filters.JobFilterSet
514
514
 
515
+ def get_object(self):
516
+ """Get the Job instance and reload the job class to ensure we have the latest version of the job code."""
517
+ obj = super().get_object()
518
+ get_job(obj.class_path, reload=True)
519
+
520
+ return obj
521
+
515
522
  @extend_schema(responses={"200": serializers.JobVariableSerializer(many=True)})
516
523
  @action(detail=True, filterset_class=None)
517
524
  def variables(self, request, *args, **kwargs):
518
525
  """Get details of the input variables that may/must be specified to run a particular Job."""
519
526
  job_model = self.get_object()
520
- job_class = get_job(job_model.class_path, reload=True)
527
+ job_class = job_model.job_class
521
528
  if job_class is None:
522
529
  raise Http404
523
530
  variables_dict = job_class._get_vars()
@@ -599,7 +606,7 @@ class JobViewSetBase(
599
606
  "One of these two flags must be removed before this job can be scheduled or run."
600
607
  )
601
608
 
602
- job_class = get_job(job_model.class_path, reload=True)
609
+ job_class = job_model.job_class
603
610
  if job_class is None:
604
611
  raise MethodNotAllowed(
605
612
  request.method, detail="This job's source code could not be located and cannot be run"
@@ -254,8 +254,8 @@ def web_request_context(
254
254
  # TODO: get_snapshots() currently requires a DB query per object change processed.
255
255
  # We need to develop a more efficient approach: https://github.com/nautobot/nautobot/issues/6303
256
256
  snapshots = oc.get_snapshots(
257
- pre_object_data.get(str(oc.changed_object_id), None),
258
- pre_object_data_v2.get(str(oc.changed_object_id), None),
257
+ pre_object_data.get(str(oc.changed_object_id), None) if pre_object_data else None,
258
+ pre_object_data_v2.get(str(oc.changed_object_id), None) if pre_object_data_v2 else None,
259
259
  )
260
260
  webhook_queryset = enqueue_webhooks(oc, snapshots=snapshots, webhook_queryset=webhook_queryset)
261
261
 
@@ -17,7 +17,7 @@ from git import InvalidGitRepositoryError, Repo
17
17
  import yaml
18
18
 
19
19
  from nautobot.core.utils.git import GitRepo
20
- from nautobot.core.utils.module_loading import import_modules_privately
20
+ from nautobot.core.utils.module_loading import check_name_safe_to_import_privately, import_modules_privately
21
21
  from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
22
22
  from nautobot.extras.choices import (
23
23
  LogLevelChoices,
@@ -720,11 +720,19 @@ def refresh_job_code_from_repository(repository_slug, skip_reimport=False, ignor
720
720
  After cloning/updating/deleting a GitRepository on disk, call this function to reload and reregister its Python.
721
721
 
722
722
  Args:
723
- repository_slug (str): Repository directory in GIT_ROOT that was updated or deleted.
724
- skip_reimport (bool): If True, unload existing code from this repository but do not re-import it.
723
+ repository_slug (str): Repository slug (directory in GIT_ROOT) that was populated, updated, or deleted.
724
+ skip_reimport (bool): If True, unload existing jobs from this repository but do not re-import them.
725
725
  ignore_import_errors (bool): If True, any exceptions raised in the import will be caught and logged.
726
726
  If False, exceptions will be re-raised after logging.
727
727
  """
728
+ # Enforced during GitRepository.clean() but just in case someone created a bad record without cleaning:
729
+ permitted, reason = check_name_safe_to_import_privately(repository_slug)
730
+ if not permitted:
731
+ logger.error("The repository_slug %r is invalid as it is %s", repository_slug, reason)
732
+ if ignore_import_errors:
733
+ return
734
+ raise ValueError(f"The repository_slug {repository_slug!r} is invalid as it is {reason}")
735
+
728
736
  # Unload any previous version of this module and its submodules if present
729
737
  for job_class_path in list(registry["jobs"]):
730
738
  if job_class_path.startswith(f"{repository_slug}."):
@@ -914,13 +914,13 @@ class ExternalIntegrationBulkEditForm(NautobotBulkEditForm):
914
914
  secrets_group = DynamicModelChoiceField(required=False, queryset=SecretsGroup.objects.all())
915
915
  verify_ssl = forms.NullBooleanField(required=False, label="Verify SSL", widget=BulkEditNullBooleanSelect)
916
916
  timeout = forms.IntegerField(required=False, min_value=0)
917
- extra_config = forms.JSONField(required=False)
917
+ extra_config = JSONField(required=False, widget=forms.Textarea, help_text="JSON data")
918
918
  http_method = forms.ChoiceField(
919
919
  required=False,
920
920
  label="HTTP Method",
921
921
  choices=add_blank_choice(WebhookHttpMethodChoices),
922
922
  )
923
- headers = forms.JSONField(required=False, label="HTTP Request headers")
923
+ headers = JSONField(required=False, widget=forms.Textarea, help_text="Headers for the HTTP request")
924
924
 
925
925
  class Meta:
926
926
  model = ExternalIntegration
@@ -1487,7 +1487,6 @@ class JobQueueBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
1487
1487
  class Meta:
1488
1488
  model = JobQueue
1489
1489
  nullable_fields = [
1490
- "secrets_group",
1491
1490
  "description",
1492
1491
  "tenant",
1493
1492
  ]
@@ -1910,8 +1909,8 @@ class RelationshipBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditFormMixin
1910
1909
  )
1911
1910
  source_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
1912
1911
  destination_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
1913
- source_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the source")
1914
- destination_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the destination")
1912
+ source_filter = JSONField(required=False, widget=forms.Textarea, help_text="Filter for the source")
1913
+ destination_filter = JSONField(required=False, widget=forms.Textarea, help_text="Filter for the destination")
1915
1914
  source_type = CSVContentTypeField(
1916
1915
  queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()), required=False
1917
1916
  )
@@ -2256,6 +2255,8 @@ class WebhookBulkEditForm(BootstrapMixin, NoteModelBulkEditFormMixin):
2256
2255
  secret = forms.CharField(required=False, max_length=CHARFIELD_MAX_LENGTH)
2257
2256
  ca_file_path = forms.CharField(required=False, max_length=4096)
2258
2257
  http_content_type = forms.CharField(required=False, max_length=CHARFIELD_MAX_LENGTH)
2258
+ additional_headers = forms.CharField(required=False, widget=forms.Textarea)
2259
+ body_template = forms.CharField(required=False, widget=forms.Textarea)
2259
2260
 
2260
2261
  # Choice field
2261
2262
  http_method = forms.ChoiceField(
@@ -2279,6 +2280,8 @@ class WebhookBulkEditForm(BootstrapMixin, NoteModelBulkEditFormMixin):
2279
2280
  "type_delete",
2280
2281
  "http_method",
2281
2282
  "http_content_type",
2283
+ "additional_headers",
2284
+ "body_template",
2282
2285
  "ssl_verification",
2283
2286
  "ca_file_path",
2284
2287
  "payload_url",
@@ -2286,6 +2289,7 @@ class WebhookBulkEditForm(BootstrapMixin, NoteModelBulkEditFormMixin):
2286
2289
  "add_content_types",
2287
2290
  "remove_content_types",
2288
2291
  )
2292
+ nullable_fields = ("additional_headers",)
2289
2293
 
2290
2294
 
2291
2295
  class WebhookForm(BootstrapMixin, forms.ModelForm):
nautobot/extras/jobs.py CHANGED
@@ -17,6 +17,7 @@ from celery.exceptions import Ignore, Reject
17
17
  from celery.utils.log import get_task_logger
18
18
  from db_file_storage.form_widgets import DBClearableFileInput
19
19
  from django import forms
20
+ from django.apps import apps
20
21
  from django.conf import settings
21
22
  from django.contrib.auth import get_user_model
22
23
  from django.core.cache import cache
@@ -1142,8 +1143,9 @@ def get_job(class_path, reload=False):
1142
1143
  # System job - not reloadable
1143
1144
  reload = False
1144
1145
  if any(class_path.startswith(f"{app_name}.") for app_name in settings.PLUGINS):
1145
- # App provided job - not reloadable
1146
- reload = False
1146
+ # App provided job - only reload if the app provides dynamic jobs
1147
+ app_config = apps.get_app_config(class_path.split(".")[0])
1148
+ reload = getattr(app_config, "provides_dynamic_jobs", False)
1147
1149
  jobs = get_jobs(reload=reload)
1148
1150
  return jobs.get(class_path, None)
1149
1151
 
@@ -269,6 +269,8 @@ class CustomFieldModel(models.Model):
269
269
  elif cf.required:
270
270
  raise ValidationError(f"Missing required custom field '{cf.key}'.")
271
271
 
272
+ clean.alters_data = True
273
+
272
274
  # Computed Field Methods
273
275
  def has_computed_fields(self, advanced_ui=None):
274
276
  """
@@ -1,7 +1,6 @@
1
1
  """Models for representing external data sources."""
2
2
 
3
3
  from contextlib import contextmanager
4
- from importlib.util import find_spec
5
4
  import logging
6
5
  import os
7
6
  import shutil
@@ -17,7 +16,8 @@ from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dash
17
16
  from nautobot.core.models.generics import PrimaryModel
18
17
  from nautobot.core.models.validators import EnhancedURLValidator
19
18
  from nautobot.core.utils.git import GitRepo
20
- from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
19
+ from nautobot.core.utils.module_loading import check_name_safe_to_import_privately
20
+ from nautobot.extras.utils import extras_features
21
21
 
22
22
  logger = logging.getLogger(__name__)
23
23
 
@@ -105,12 +105,9 @@ class GitRepository(PrimaryModel):
105
105
  )
106
106
 
107
107
  if not self.present_in_database:
108
- check_if_key_is_graphql_safe(self.__class__.__name__, self.slug, "slug")
109
- # Check on create whether the proposed slug conflicts with a module name already in the Python environment.
110
- if find_spec(self.slug) is not None:
111
- raise ValidationError(
112
- f'Please choose a different slug, as "{self.slug}" is an installed Python package or module.'
113
- )
108
+ permitted, reason = check_name_safe_to_import_privately(self.slug)
109
+ if not permitted:
110
+ raise ValidationError({"slug": f"Please choose a different slug; {self.slug!r} is {reason}"})
114
111
 
115
112
  if self.provided_contents:
116
113
  q = models.Q()
@@ -180,6 +177,8 @@ class GitRepository(PrimaryModel):
180
177
  return enqueue_git_repository_diff_origin_and_local(self, user)
181
178
  return enqueue_pull_git_repository_and_refresh_data(self, user)
182
179
 
180
+ sync.alters_data = True
181
+
183
182
  @contextmanager
184
183
  def clone_to_directory_context(self, path=None, branch=None, head=None, depth=0):
185
184
  """
@@ -207,6 +206,8 @@ class GitRepository(PrimaryModel):
207
206
  if path_name:
208
207
  self.cleanup_cloned_directory(path_name)
209
208
 
209
+ clone_to_directory_context.alters_data = True
210
+
210
211
  def clone_to_directory(self, path=None, branch=None, head=None, depth=0):
211
212
  """
212
213
  Perform a (shallow or full) clone of the Git repository in a temporary directory.
@@ -246,6 +247,8 @@ class GitRepository(PrimaryModel):
246
247
  logger.info(f"Cloned repository {self.name} to {path_name}")
247
248
  return path_name
248
249
 
250
+ clone_to_directory.alters_data = True
251
+
249
252
  def cleanup_cloned_directory(self, path):
250
253
  """
251
254
  Cleanup the cloned directory.
@@ -259,3 +262,5 @@ class GitRepository(PrimaryModel):
259
262
  except OSError as os_error:
260
263
  # log error if the cleanup fails
261
264
  logger.error(f"Failed to cleanup temporary directory at {path}: {os_error}")
265
+
266
+ cleanup_cloned_directory.alters_data = True
@@ -338,6 +338,8 @@ class DynamicGroup(PrimaryModel):
338
338
 
339
339
  return self.members
340
340
 
341
+ _set_members.alters_data = True
342
+
341
343
  def add_members(self, objects_to_add):
342
344
  """Add the given list or QuerySet of objects to this staticly defined group."""
343
345
  if self.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
@@ -354,6 +356,8 @@ class DynamicGroup(PrimaryModel):
354
356
  objects_to_add = [obj for obj in objects_to_add if obj not in existing_members]
355
357
  return self._add_members(objects_to_add)
356
358
 
359
+ add_members.alters_data = True
360
+
357
361
  def _add_members(self, objects_to_add):
358
362
  """
359
363
  Internal API for adding the given list or QuerySet of objects to the cached/static members of this group.
@@ -377,6 +381,8 @@ class DynamicGroup(PrimaryModel):
377
381
  ]
378
382
  StaticGroupAssociation.all_objects.bulk_create(sgas, batch_size=1000)
379
383
 
384
+ _add_members.alters_data = True
385
+
380
386
  def remove_members(self, objects_to_remove):
381
387
  """Remove the given list or QuerySet of objects from this staticly defined group."""
382
388
  if self.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
@@ -390,6 +396,8 @@ class DynamicGroup(PrimaryModel):
390
396
  raise TypeError(f"{obj} is not a {self.model._meta.label_lower}")
391
397
  return self._remove_members(objects_to_remove)
392
398
 
399
+ remove_members.alters_data = True
400
+
393
401
  def _remove_members(self, objects_to_remove):
394
402
  """Internal API for removing the given list or QuerySet from the cached/static members of this Group."""
395
403
  from nautobot.extras.signals import _handle_deleted_object # avoid circular import
@@ -418,6 +426,8 @@ class DynamicGroup(PrimaryModel):
418
426
  logger.debug("Re-connecting the _handle_deleted_object signal")
419
427
  pre_delete.connect(_handle_deleted_object)
420
428
 
429
+ _remove_members.alters_data = True
430
+
421
431
  @property
422
432
  @method_deprecated("Members are now cached in the database via StaticGroupAssociations rather than in Redis.")
423
433
  def members_cache_key(self):
@@ -451,6 +461,8 @@ class DynamicGroup(PrimaryModel):
451
461
 
452
462
  return members
453
463
 
464
+ update_cached_members.alters_data = True
465
+
454
466
  def has_member(self, obj, use_cache=False):
455
467
  """
456
468
  Return True if the given object is a member of this group.
@@ -560,6 +572,8 @@ class DynamicGroup(PrimaryModel):
560
572
 
561
573
  self.filter = new_filter
562
574
 
575
+ set_filter.alters_data = True
576
+
563
577
  def get_initial(self):
564
578
  """
565
579
  Return a form-friendly version of `self.filter` for initial form data.
@@ -815,6 +829,8 @@ class DynamicGroup(PrimaryModel):
815
829
  instance = self.children.through(parent_group=self, group=child, operator=operator, weight=weight)
816
830
  return instance.validated_save()
817
831
 
832
+ add_child.alters_data = True
833
+
818
834
  # TODO: unused in core
819
835
  def remove_child(self, child):
820
836
  """
@@ -829,6 +845,8 @@ class DynamicGroup(PrimaryModel):
829
845
  instance = self.children.through.objects.get(parent_group=self, group=child)
830
846
  return instance.delete()
831
847
 
848
+ remove_child.alters_data = True
849
+
832
850
  def get_descendants(self, group=None):
833
851
  """
834
852
  Recursively return a list of the children of all child groups.
@@ -753,6 +753,8 @@ class JobResult(BaseModel, CustomFieldModel):
753
753
  duration.total_seconds()
754
754
  )
755
755
 
756
+ set_status.alters_data = True
757
+
756
758
  @classmethod
757
759
  def execute_job(cls, *args, **kwargs):
758
760
  """
@@ -768,6 +770,8 @@ class JobResult(BaseModel, CustomFieldModel):
768
770
  """
769
771
  return cls.enqueue_job(*args, **kwargs, synchronous=True)
770
772
 
773
+ execute_job.__func__.alters_data = True
774
+
771
775
  @classmethod
772
776
  def enqueue_job(
773
777
  cls,
@@ -938,6 +942,8 @@ class JobResult(BaseModel, CustomFieldModel):
938
942
 
939
943
  return job_result
940
944
 
945
+ enqueue_job.__func__.alters_data = True
946
+
941
947
  def log(
942
948
  self,
943
949
  message,
@@ -989,6 +995,8 @@ class JobResult(BaseModel, CustomFieldModel):
989
995
  else:
990
996
  log.save(using=JOB_LOGS)
991
997
 
998
+ log.alters_data = True
999
+
992
1000
 
993
1001
  #
994
1002
  # Job Button
@@ -1076,6 +1084,8 @@ class ScheduledJobs(models.Model):
1076
1084
  if not instance.no_changes:
1077
1085
  cls.update_changed()
1078
1086
 
1087
+ changed.__func__.alters_data = True
1088
+
1079
1089
  @classmethod
1080
1090
  def update_changed(cls, raw=False, **kwargs):
1081
1091
  """This function acts as a signal handler to track changes to the scheduled job that is triggered after a change"""
@@ -1083,6 +1093,8 @@ class ScheduledJobs(models.Model):
1083
1093
  return
1084
1094
  cls.objects.update_or_create(ident=1, defaults={"last_update": timezone.now()})
1085
1095
 
1096
+ update_changed.__func__.alters_data = True
1097
+
1086
1098
  @classmethod
1087
1099
  def last_change(cls):
1088
1100
  """This function acts as a getter for the last update on scheduled jobs"""
@@ -1371,6 +1383,11 @@ class ScheduledJob(BaseModel):
1371
1383
  if job_model.time_limit > 0:
1372
1384
  celery_kwargs["time_limit"] = job_model.time_limit
1373
1385
 
1386
+ # We do this because when a job creates an approval workflow, a scheduled job is also created.
1387
+ # If the scheduled job has an "immediate" interval, the scheduler will not send this task.
1388
+ # since TYPE_IMMEDIATELY is not a valid value in JobExecutionType.SCHEDULE_CHOICES
1389
+ if interval == JobExecutionType.TYPE_IMMEDIATELY:
1390
+ interval = JobExecutionType.TYPE_FUTURE
1374
1391
  # 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
1375
1392
  #
1376
1393
  # We pass in task and job_model here partly for forward/backward compatibility logic, and
@@ -1397,6 +1414,8 @@ class ScheduledJob(BaseModel):
1397
1414
  scheduled_job.validated_save()
1398
1415
  return scheduled_job
1399
1416
 
1417
+ create_schedule.__func__.alters_data = True
1418
+
1400
1419
  def to_cron(self):
1401
1420
  tz = self.time_zone
1402
1421
  t = self.start_time.astimezone(tz) # pylint: disable=no-member
@@ -251,6 +251,8 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
251
251
  self.clean()
252
252
  return super().validated_save(*args, **kwargs)
253
253
 
254
+ validated_save.alters_data = True
255
+
254
256
  def clean(self):
255
257
  """
256
258
  Validate a value according to the field's type validation rules.
@@ -457,6 +457,8 @@ class ExportTemplate(
457
457
  if self.file_extension.startswith("."):
458
458
  self.file_extension = self.file_extension[1:]
459
459
 
460
+ clean.alters_data = True
461
+
460
462
 
461
463
  #
462
464
  # External integrations
@@ -919,6 +921,8 @@ class UserSavedViewAssociation(BaseModel):
919
921
  super().clean()
920
922
  self.view_name = self.saved_view.view
921
923
 
924
+ clean.alters_data = True
925
+
922
926
 
923
927
  #
924
928
  # Webhooks
@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
4
4
  from django.core.serializers.json import DjangoJSONEncoder
5
5
  from django.db import models
6
6
  from jinja2.exceptions import TemplateSyntaxError, UndefinedError
7
+ from jinja2.sandbox import unsafe
7
8
 
8
9
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
9
10
  from nautobot.core.models import BaseModel
@@ -58,6 +59,7 @@ class Secret(PrimaryModel):
58
59
  except (TemplateSyntaxError, UndefinedError) as exc:
59
60
  raise SecretParametersError(self, registry["secrets_providers"].get(self.provider), str(exc)) from exc
60
61
 
62
+ @unsafe
61
63
  def get_value(self, obj=None):
62
64
  """Retrieve the secret value that this Secret is a representation of.
63
65
 
@@ -77,6 +79,8 @@ class Secret(PrimaryModel):
77
79
  except Exception as exc:
78
80
  raise SecretError(self, provider, str(exc)) from exc
79
81
 
82
+ get_value.do_not_call_in_templates = True
83
+
80
84
  def clean(self):
81
85
  provider = registry["secrets_providers"].get(self.provider)
82
86
  if not provider:
@@ -108,6 +112,7 @@ class SecretsGroup(OrganizationalModel):
108
112
  def __str__(self):
109
113
  return self.name
110
114
 
115
+ @unsafe
111
116
  def get_secret_value(self, access_type, secret_type, obj=None, **kwargs):
112
117
  """Helper method to retrieve a specific secret from this group.
113
118
 
@@ -118,6 +123,8 @@ class SecretsGroup(OrganizationalModel):
118
123
  ).secret
119
124
  return secret.get_value(obj=obj, **kwargs)
120
125
 
126
+ get_secret_value.do_not_call_in_templates = True
127
+
121
128
 
122
129
  @extras_features(
123
130
  "graphql",
@@ -81,6 +81,9 @@ class NautobotAppConfig(NautobotConfig):
81
81
  config_view_name = None
82
82
  docs_view_name = None
83
83
 
84
+ # Dynamic jobs. Set to True if the app's job code should be reloaded at runtime
85
+ provides_dynamic_jobs = False
86
+
84
87
  # Default integration paths. Plugin authors can override these to customize the paths to
85
88
  # integrated components.
86
89
  banner_function = "banner.banner"
@@ -1,5 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
+ from jinja2.sandbox import unsafe
4
+
3
5
  from nautobot.extras.registry import registry
4
6
 
5
7
  from .exceptions import SecretError, SecretParametersError, SecretProviderError, SecretValueNotFoundError
@@ -31,6 +33,7 @@ class SecretsProvider(ABC):
31
33
 
32
34
  @classmethod
33
35
  @abstractmethod
36
+ @unsafe
34
37
  def get_value_for_secret(cls, secret, obj=None, **kwargs):
35
38
  """Retrieve the stored value described by the given Secret record.
36
39
 
@@ -41,6 +44,17 @@ class SecretsProvider(ABC):
41
44
  obj (object): Django model instance or similar providing additional context for retrieving the secret.
42
45
  """
43
46
 
47
+ get_value_for_secret.__func__.do_not_call_in_templates = True
48
+
49
+ def __init_subclass__(cls, **kwargs):
50
+ # Automatically apply protection against Django and Jinja2 template execution to child classes.
51
+ if not getattr(cls.get_value_for_secret, "do_not_call_in_templates", False): # Django
52
+ cls.get_value_for_secret.__func__.do_not_call_in_templates = True
53
+ if not getattr(cls.get_value_for_secret, "unsafe_callable", False): # Jinja @unsafe decorator
54
+ cls.get_value_for_secret.__func__.unsafe_callable = True
55
+
56
+ super().__init_subclass__(**kwargs)
57
+
44
58
 
45
59
  def register_secrets_provider(provider):
46
60
  """
nautobot/extras/tables.py CHANGED
@@ -1,9 +1,13 @@
1
1
  from django.conf import settings
2
+ from django.db.models import QuerySet
2
3
  from django.utils.html import format_html, format_html_join
3
4
  import django_tables2 as tables
5
+ from django_tables2.data import TableData
6
+ from django_tables2.rows import BoundRows
4
7
  from django_tables2.utils import Accessor
5
8
  from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
6
9
 
10
+ from nautobot.core.models.querysets import count_related
7
11
  from nautobot.core.tables import (
8
12
  BaseTable,
9
13
  BooleanColumn,
@@ -19,7 +23,7 @@ from nautobot.core.tables import (
19
23
  from nautobot.core.templatetags.helpers import render_boolean, render_json, render_markdown
20
24
  from nautobot.tenancy.tables import TenantColumn
21
25
 
22
- from .choices import MetadataTypeDataTypeChoices
26
+ from .choices import LogLevelChoices, MetadataTypeDataTypeChoices
23
27
  from .models import (
24
28
  ComputedField,
25
29
  ConfigContext,
@@ -891,7 +895,7 @@ class JobResultTable(BaseTable):
891
895
  )
892
896
  summary = tables.Column(
893
897
  empty_values=(),
894
- verbose_name="Results",
898
+ verbose_name="Summary",
895
899
  orderable=False,
896
900
  attrs={"td": {"class": "text-nowrap report-stats"}},
897
901
  )
@@ -901,6 +905,40 @@ class JobResultTable(BaseTable):
901
905
  )
902
906
  actions = ButtonsColumn(JobResult, buttons=("delete",), prepend_template=JOB_RESULT_BUTTONS)
903
907
 
908
+ def __init__(self, *args, **kwargs):
909
+ super().__init__(*args, **kwargs)
910
+ # Only calculate log counts for "summary" column if it's actually visible.
911
+ if self.columns["summary"].visible and isinstance(self.data.data, QuerySet):
912
+ self.data = TableData.from_data(
913
+ self.data.data.annotate(
914
+ debug_log_count=count_related(
915
+ JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_DEBUG}
916
+ ),
917
+ success_log_count=count_related(
918
+ JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_SUCCESS}
919
+ ),
920
+ info_log_count=count_related(
921
+ JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_INFO}
922
+ ),
923
+ warning_log_count=count_related(
924
+ JobLogEntry, "job_result", filter_dict={"log_level": LogLevelChoices.LOG_WARNING}
925
+ ),
926
+ error_log_count=count_related(
927
+ JobLogEntry,
928
+ "job_result",
929
+ filter_dict={
930
+ "log_level__in": [
931
+ LogLevelChoices.LOG_FAILURE,
932
+ LogLevelChoices.LOG_ERROR,
933
+ LogLevelChoices.LOG_CRITICAL,
934
+ ],
935
+ },
936
+ ),
937
+ )
938
+ )
939
+ self.data.set_table(self)
940
+ self.rows = BoundRows(data=self.data, table=self, pinned_data=self.pinned_data)
941
+
904
942
  def render_summary(self, record):
905
943
  """
906
944
  Define custom rendering for the summary column.
@@ -940,7 +978,6 @@ class JobResultTable(BaseTable):
940
978
  "job_model",
941
979
  "user",
942
980
  "status",
943
- "summary",
944
981
  "actions",
945
982
  )
946
983