nautobot 3.0.3__py3-none-any.whl → 3.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. nautobot/core/authentication.py +0 -1
  2. nautobot/core/celery/schedulers.py +1 -3
  3. nautobot/core/cli/__init__.py +81 -39
  4. nautobot/core/settings.yaml +12 -4
  5. nautobot/core/tables.py +28 -17
  6. nautobot/core/templates/graphene/graphiql.html +3 -5
  7. nautobot/core/templates/inc/javascript.html +5 -10
  8. nautobot/core/templates/inc/media.html +5 -4
  9. nautobot/core/templates/inc/media_failure.html +73 -0
  10. nautobot/core/templates/media_failure.html +1 -0
  11. nautobot/core/tests/test_cli.py +120 -1
  12. nautobot/core/tests/test_templatetags_helpers.py +9 -9
  13. nautobot/core/ui/object_detail.py +1 -0
  14. nautobot/dcim/forms.py +1 -0
  15. nautobot/dcim/tables/devices.py +6 -5
  16. nautobot/dcim/tables/template_code.py +8 -4
  17. nautobot/dcim/templates/dcim/platform_create.html +3 -4
  18. nautobot/dcim/tests/test_tables.py +5 -6
  19. nautobot/dcim/views.py +6 -7
  20. nautobot/extras/models/jobs.py +7 -1
  21. nautobot/extras/signals.py +143 -113
  22. nautobot/extras/tables.py +3 -3
  23. nautobot/extras/templates/extras/inc/jobresult_js.html +1 -2
  24. nautobot/extras/templates/extras/scheduledjob.html +3 -1
  25. nautobot/extras/tests/test_customfields.py +75 -9
  26. nautobot/extras/tests/test_utils.py +116 -1
  27. nautobot/extras/utils.py +18 -16
  28. nautobot/extras/views.py +2 -14
  29. nautobot/ipam/apps.py +1 -0
  30. nautobot/ipam/filters.py +58 -3
  31. nautobot/ipam/tables.py +8 -4
  32. nautobot/ipam/tests/test_filters.py +55 -0
  33. nautobot/project-static/dist/css/nautobot.css +1 -1
  34. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  35. nautobot/project-static/docs/404.html +64 -8
  36. nautobot/project-static/docs/apps/index.html +64 -8
  37. nautobot/project-static/docs/apps/nautobot-apps.html +64 -8
  38. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +64 -8
  39. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +64 -8
  40. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +64 -8
  41. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +64 -8
  42. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +64 -8
  43. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +64 -8
  44. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +64 -8
  45. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +64 -8
  46. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +64 -8
  47. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +64 -8
  48. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +64 -8
  49. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +64 -8
  50. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +64 -8
  51. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +64 -8
  52. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +64 -8
  53. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +64 -8
  54. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +64 -8
  55. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +64 -8
  56. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +64 -8
  57. nautobot/project-static/docs/code-reference/nautobot/apps/templatetags.html +64 -8
  58. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +64 -8
  59. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +64 -8
  60. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +64 -8
  61. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +64 -8
  62. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +64 -8
  63. nautobot/project-static/docs/development/apps/api/configuration-view.html +64 -8
  64. nautobot/project-static/docs/development/apps/api/database-backend-config.html +64 -8
  65. nautobot/project-static/docs/development/apps/api/models/django-admin.html +67 -11
  66. nautobot/project-static/docs/development/apps/api/models/global-search.html +64 -8
  67. nautobot/project-static/docs/development/apps/api/models/graphql.html +64 -8
  68. nautobot/project-static/docs/development/apps/api/models/index.html +64 -8
  69. nautobot/project-static/docs/development/apps/api/models/queryset.html +13440 -0
  70. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +64 -8
  71. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +64 -8
  72. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +64 -8
  73. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +64 -8
  74. nautobot/project-static/docs/development/apps/api/platform-features/index.html +64 -8
  75. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +64 -8
  76. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +64 -8
  77. nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +64 -8
  78. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +64 -8
  79. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +64 -8
  80. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +64 -8
  81. nautobot/project-static/docs/development/apps/api/prometheus.html +64 -8
  82. nautobot/project-static/docs/development/apps/api/setup.html +64 -8
  83. nautobot/project-static/docs/development/apps/api/testing.html +64 -8
  84. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +64 -8
  85. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +64 -8
  86. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +64 -8
  87. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +64 -8
  88. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +64 -8
  89. nautobot/project-static/docs/development/apps/api/views/base-template.html +64 -8
  90. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +64 -8
  91. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +64 -8
  92. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +64 -8
  93. nautobot/project-static/docs/development/apps/api/views/index.html +67 -11
  94. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +64 -8
  95. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +64 -8
  96. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +64 -8
  97. nautobot/project-static/docs/development/apps/api/views/notes.html +64 -8
  98. nautobot/project-static/docs/development/apps/api/views/rest-api.html +64 -8
  99. nautobot/project-static/docs/development/apps/api/views/urls.html +64 -8
  100. nautobot/project-static/docs/development/apps/index.html +64 -8
  101. nautobot/project-static/docs/development/apps/migration/code-updates.html +64 -8
  102. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +64 -8
  103. nautobot/project-static/docs/development/apps/migration/from-v1.html +64 -8
  104. nautobot/project-static/docs/development/apps/migration/from-v2/migrating-v2-to-v3.html +64 -8
  105. nautobot/project-static/docs/development/apps/migration/from-v2/new-nautobot-custom-ui-apis.html +64 -8
  106. nautobot/project-static/docs/development/apps/migration/from-v2/overview.html +68 -8
  107. nautobot/project-static/docs/development/apps/migration/from-v2/upgrading-from-bootstrap-v3-to-v5.html +64 -8
  108. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +64 -8
  109. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +64 -8
  110. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +64 -8
  111. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +64 -8
  112. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +64 -8
  113. nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +64 -8
  114. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +64 -8
  115. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +64 -8
  116. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +64 -8
  117. nautobot/project-static/docs/development/apps/porting-from-netbox.html +64 -8
  118. nautobot/project-static/docs/development/core/application-registry.html +64 -8
  119. nautobot/project-static/docs/development/core/best-practices.html +64 -8
  120. nautobot/project-static/docs/development/core/caching.html +64 -8
  121. nautobot/project-static/docs/development/core/controllers.html +64 -8
  122. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +64 -8
  123. nautobot/project-static/docs/development/core/docs-media-standards.html +64 -8
  124. nautobot/project-static/docs/development/core/generic-views.html +64 -8
  125. nautobot/project-static/docs/development/core/getting-started.html +64 -8
  126. nautobot/project-static/docs/development/core/homepage.html +64 -8
  127. nautobot/project-static/docs/development/core/index.html +64 -8
  128. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +64 -8
  129. nautobot/project-static/docs/development/core/model-checklist.html +64 -8
  130. nautobot/project-static/docs/development/core/model-features.html +64 -8
  131. nautobot/project-static/docs/development/core/natural-keys.html +64 -8
  132. nautobot/project-static/docs/development/core/navigation-menu.html +64 -8
  133. nautobot/project-static/docs/development/core/release-checklist.html +66 -8
  134. nautobot/project-static/docs/development/core/role-internals.html +64 -8
  135. nautobot/project-static/docs/development/core/settings.html +64 -8
  136. nautobot/project-static/docs/development/core/style-guide.html +64 -8
  137. nautobot/project-static/docs/development/core/templates.html +64 -8
  138. nautobot/project-static/docs/development/core/testing.html +64 -8
  139. nautobot/project-static/docs/development/core/ui-best-practices.html +64 -8
  140. nautobot/project-static/docs/development/core/ui-component-framework.html +64 -8
  141. nautobot/project-static/docs/development/core/user-preferences.html +64 -8
  142. nautobot/project-static/docs/development/index.html +64 -8
  143. nautobot/project-static/docs/development/jobs/getting-started.html +64 -8
  144. nautobot/project-static/docs/development/jobs/index.html +64 -8
  145. nautobot/project-static/docs/development/jobs/installation.html +64 -8
  146. nautobot/project-static/docs/development/jobs/job-extensions.html +64 -8
  147. nautobot/project-static/docs/development/jobs/job-logging.html +64 -8
  148. nautobot/project-static/docs/development/jobs/job-patterns.html +64 -8
  149. nautobot/project-static/docs/development/jobs/job-structure.html +64 -8
  150. nautobot/project-static/docs/development/jobs/migration/from-v1.html +64 -8
  151. nautobot/project-static/docs/development/jobs/testing.html +64 -8
  152. nautobot/project-static/docs/index.html +64 -8
  153. nautobot/project-static/docs/overview/application_stack.html +64 -8
  154. nautobot/project-static/docs/overview/design_philosophy.html +64 -8
  155. nautobot/project-static/docs/release-notes/index.html +64 -8
  156. nautobot/project-static/docs/release-notes/version-1.0.html +64 -8
  157. nautobot/project-static/docs/release-notes/version-1.1.html +64 -8
  158. nautobot/project-static/docs/release-notes/version-1.2.html +65 -9
  159. nautobot/project-static/docs/release-notes/version-1.3.html +64 -8
  160. nautobot/project-static/docs/release-notes/version-1.4.html +64 -8
  161. nautobot/project-static/docs/release-notes/version-1.5.html +64 -8
  162. nautobot/project-static/docs/release-notes/version-1.6.html +64 -8
  163. nautobot/project-static/docs/release-notes/version-2.0.html +64 -8
  164. nautobot/project-static/docs/release-notes/version-2.1.html +64 -8
  165. nautobot/project-static/docs/release-notes/version-2.2.html +64 -8
  166. nautobot/project-static/docs/release-notes/version-2.3.html +64 -8
  167. nautobot/project-static/docs/release-notes/version-2.4.html +584 -8
  168. nautobot/project-static/docs/release-notes/version-3.0.html +467 -8
  169. nautobot/project-static/docs/search/search_index.json +1 -1
  170. nautobot/project-static/docs/sitemap.xml +337 -329
  171. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  172. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +64 -8
  173. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +64 -8
  174. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +64 -8
  175. nautobot/project-static/docs/user-guide/administration/configuration/index.html +64 -8
  176. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +64 -8
  177. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +75 -12
  178. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -8
  179. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +64 -8
  180. nautobot/project-static/docs/user-guide/administration/guides/docker.html +64 -8
  181. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +64 -8
  182. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -8
  183. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +64 -8
  184. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +64 -8
  185. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +64 -8
  186. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +64 -8
  187. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +64 -8
  188. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +64 -8
  189. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +64 -8
  190. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +72 -9
  191. nautobot/project-static/docs/user-guide/administration/installation/index.html +64 -8
  192. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +64 -8
  193. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +64 -8
  194. nautobot/project-static/docs/user-guide/administration/installation/services.html +64 -8
  195. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +65 -9
  196. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +64 -8
  197. nautobot/project-static/docs/user-guide/administration/security/index.html +64 -8
  198. nautobot/project-static/docs/user-guide/administration/security/notices.html +64 -8
  199. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +64 -8
  200. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +64 -8
  201. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +67 -11
  202. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +64 -8
  203. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +64 -8
  204. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +64 -8
  205. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +64 -8
  206. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +64 -8
  207. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +64 -8
  208. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +64 -8
  209. nautobot/project-static/docs/user-guide/administration/upgrading/from-v2/index.html +68 -8
  210. nautobot/project-static/docs/user-guide/administration/upgrading/postgresql.html +13391 -0
  211. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +125 -15
  212. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +64 -8
  213. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +64 -8
  214. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +64 -8
  215. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +64 -8
  216. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +64 -8
  217. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +64 -8
  218. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +64 -8
  219. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +64 -8
  220. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +64 -8
  221. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +64 -8
  222. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +64 -8
  223. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +64 -8
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +64 -8
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +64 -8
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +64 -8
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +64 -8
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +64 -8
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +64 -8
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +64 -8
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +64 -8
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +64 -8
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +64 -8
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +64 -8
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +64 -8
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +64 -8
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +64 -8
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +64 -8
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +64 -8
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +64 -8
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +64 -8
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +64 -8
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +64 -8
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +64 -8
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +64 -8
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +64 -8
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +64 -8
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +64 -8
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +64 -8
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +64 -8
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +64 -8
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +64 -8
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +64 -8
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +64 -8
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +64 -8
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +64 -8
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +64 -8
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +64 -8
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +64 -8
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +64 -8
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +64 -8
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +64 -8
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +64 -8
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +64 -8
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +64 -8
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +64 -8
  267. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +64 -8
  268. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +64 -8
  269. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +64 -8
  270. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +64 -8
  271. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +64 -8
  272. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +64 -8
  273. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +64 -8
  274. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +64 -8
  275. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +64 -8
  276. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +64 -8
  277. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +64 -8
  278. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +64 -8
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +64 -8
  280. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/certificateprofile.html +64 -8
  281. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/healthcheckmonitor.html +64 -8
  282. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/index.html +64 -8
  283. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpool.html +64 -8
  284. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpoolmember.html +64 -8
  285. nautobot/project-static/docs/user-guide/core-data-model/load-balancers/virtualserver.html +64 -8
  286. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +64 -8
  287. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +64 -8
  288. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +64 -8
  289. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +64 -8
  290. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +64 -8
  291. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +64 -8
  292. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +64 -8
  293. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +64 -8
  294. nautobot/project-static/docs/user-guide/core-data-model/vpn/index.html +64 -8
  295. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpn.html +64 -8
  296. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase1policy.html +64 -8
  297. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase2policy.html +64 -8
  298. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnprofile.html +64 -8
  299. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnel.html +64 -8
  300. nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnelendpoint.html +64 -8
  301. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +64 -8
  302. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +64 -8
  303. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +64 -8
  304. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +64 -8
  305. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +64 -8
  306. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +64 -8
  307. nautobot/project-static/docs/user-guide/feature-guides/data-compliance.html +64 -8
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +64 -8
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +64 -8
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +64 -8
  311. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +64 -8
  312. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +64 -8
  313. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +64 -8
  314. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +64 -8
  315. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +75 -12
  316. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +85 -17
  317. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +64 -8
  318. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +64 -8
  319. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-dark.png +0 -0
  320. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-light.png +0 -0
  321. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-dark.png +0 -0
  322. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-light.png +0 -0
  323. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-dark.png +0 -0
  324. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-light.png +0 -0
  325. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-dark.png +0 -0
  326. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-light.png +0 -0
  327. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-dark.png +0 -0
  328. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-light.png +0 -0
  329. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-dark.png +0 -0
  330. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-light.png +0 -0
  331. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-dark.png +0 -0
  332. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-light.png +0 -0
  333. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-dark.png +0 -0
  334. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-light.png +0 -0
  335. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +64 -8
  336. nautobot/project-static/docs/user-guide/feature-guides/load-balancers.html +64 -8
  337. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +64 -8
  338. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +64 -8
  339. nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +64 -8
  340. nautobot/project-static/docs/user-guide/index.html +64 -8
  341. nautobot/project-static/docs/user-guide/platform-functionality/approval-workflow.html +64 -8
  342. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +64 -8
  343. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +64 -8
  344. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +64 -8
  345. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +64 -8
  346. nautobot/project-static/docs/user-guide/platform-functionality/data-validation.html +64 -8
  347. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +64 -8
  348. nautobot/project-static/docs/user-guide/platform-functionality/echarts.html +64 -8
  349. nautobot/project-static/docs/user-guide/platform-functionality/events.html +64 -8
  350. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +64 -8
  351. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +64 -8
  352. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +64 -8
  353. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +64 -8
  354. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +64 -8
  355. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +64 -8
  356. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +64 -8
  357. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +64 -8
  358. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +64 -8
  359. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +64 -8
  360. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +64 -8
  361. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +64 -8
  362. nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +64 -8
  363. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +64 -8
  364. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +64 -8
  365. nautobot/project-static/docs/user-guide/platform-functionality/note.html +64 -8
  366. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +64 -8
  367. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +64 -8
  368. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +64 -8
  369. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +64 -8
  370. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +116 -34
  371. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +64 -8
  372. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +64 -8
  373. nautobot/project-static/docs/user-guide/platform-functionality/role.html +64 -8
  374. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +64 -8
  375. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +64 -8
  376. nautobot/project-static/docs/user-guide/platform-functionality/status.html +64 -8
  377. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +64 -8
  378. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +64 -8
  379. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/configurablecolumns.html +64 -8
  380. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/savedview.html +64 -8
  381. nautobot/project-static/docs/user-guide/platform-functionality/user-interface/search.html +64 -8
  382. nautobot/project-static/docs/user-guide/platform-functionality/users/groups.html +64 -8
  383. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +64 -8
  384. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +64 -8
  385. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +64 -8
  386. nautobot/tenancy/tables.py +1 -1
  387. nautobot/ui/package-lock.json +36 -36
  388. nautobot/ui/package.json +3 -3
  389. nautobot/ui/src/scss/nautobot.scss +2 -1
  390. nautobot/users/models.py +33 -0
  391. nautobot/users/tests/test_models.py +83 -0
  392. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/METADATA +4 -4
  393. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/RECORD +397 -386
  394. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant.png +0 -0
  395. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device.png +0 -0
  396. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2.png +0 -0
  397. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans.png +0 -0
  398. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2.png +0 -0
  399. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page.png +0 -0
  400. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface.png +0 -0
  401. nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2.png +0 -0
  402. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/LICENSE.txt +0 -0
  403. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/NOTICE +0 -0
  404. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/WHEEL +0 -0
  405. {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/entry_points.txt +0 -0
@@ -52,7 +52,6 @@ class ObjectPermissionBackend(ModelBackend):
52
52
  return user_obj.is_active and (user_obj.is_staff or user_obj.is_superuser)
53
53
 
54
54
  app_label, _action, model_name = resolve_permission(perm)
55
-
56
55
  if app_label == "users" and model_name == "admingroup":
57
56
  perm = perm.replace("users", "auth").replace("admingroup", "group")
58
57
 
@@ -138,9 +138,7 @@ class NautobotDatabaseScheduler(DatabaseScheduler):
138
138
  task_name=scheduled_job.job_model.class_path,
139
139
  celery_kwargs=entry.options,
140
140
  )
141
- job_result = run_kubernetes_job_and_return_job_result(
142
- job_queue, job_result, json.dumps(entry_kwargs)
143
- )
141
+ job_result = run_kubernetes_job_and_return_job_result(job_result, json.dumps(entry_kwargs))
144
142
  # Return an AsyncResult object to mimic the behavior of Celery tasks after the job is finished by Kubernetes Job Pod.
145
143
  resp = AsyncResult(job_result.id)
146
144
  else:
@@ -3,7 +3,9 @@ Utilities and primitives for the `nautobot-server` CLI command.
3
3
  """
4
4
 
5
5
  import argparse
6
+ from copy import deepcopy
6
7
  import importlib.util
8
+ import logging
7
9
  import os
8
10
  import sys
9
11
 
@@ -34,43 +36,46 @@ USAGE = """%(prog)s --help
34
36
  %(prog)s [-c CONFIG_PATH] SUBCOMMAND ..."""
35
37
 
36
38
 
37
- def _preprocess_settings(settings, config_path):
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ def _preprocess_settings(settings_module, config_path):
38
43
  """
39
44
  After loading nautobot_config.py and nautobot.core.settings, but before starting Django, modify the settings module.
40
45
 
41
- - Set settings.SETTINGS_PATH for ease of reference
46
+ - Set settings_module.SETTINGS_PATH for ease of reference
42
47
  - Handle `EXTRA_*` settings
43
48
  - Create Nautobot storage directories if they don't already exist
44
49
  - Change database backends to django-prometheus if appropriate
45
50
  - Set up 'job_logs' database mirror
46
51
  - Handle our custom `STORAGE_BACKEND` setting.
47
- - Load plugins based on settings.PLUGINS (potentially affecting INSTALLED_APPS, MIDDLEWARE, and CONSTANCE_CONFIG)
48
- - Load event brokers based on settings.EVENT_BROKERS
52
+ - Load plugins based on settings_module.PLUGINS (may affect INSTALLED_APPS, MIDDLEWARE, and CONSTANCE_CONFIG)
53
+ - Load event brokers based on settings_module.EVENT_BROKERS
49
54
  """
50
- settings.SETTINGS_PATH = config_path
55
+ settings_module.SETTINGS_PATH = config_path
51
56
 
52
57
  # Any setting that starts with EXTRA_ and matches a setting that is a list or tuple
53
58
  # will automatically append the values to the current setting.
54
59
  # "It might make sense to make this less magical"
55
60
  extras = {}
56
- for setting in dir(settings):
61
+ for setting in dir(settings_module):
57
62
  if setting == setting.upper() and setting.startswith("EXTRA_"):
58
63
  base_setting = setting[6:]
59
- if isinstance(getattr(settings, base_setting), (list, tuple)):
60
- extras[base_setting] = getattr(settings, setting)
64
+ if isinstance(getattr(settings_module, base_setting), (list, tuple)):
65
+ extras[base_setting] = getattr(settings_module, setting)
61
66
  for base_setting, extra_values in extras.items():
62
- base_value = getattr(settings, base_setting)
63
- setattr(settings, base_setting, base_value + type(base_value)(extra_values))
67
+ base_value = getattr(settings_module, base_setting)
68
+ setattr(settings_module, base_setting, base_value + type(base_value)(extra_values))
64
69
 
65
70
  #
66
71
  # Storage directories
67
72
  #
68
- os.makedirs(settings.GIT_ROOT, exist_ok=True)
69
- os.makedirs(settings.JOBS_ROOT, exist_ok=True)
70
- os.makedirs(settings.MEDIA_ROOT, exist_ok=True)
71
- os.makedirs(os.path.join(settings.MEDIA_ROOT, "devicetype-images"), exist_ok=True)
72
- os.makedirs(os.path.join(settings.MEDIA_ROOT, "image-attachments"), exist_ok=True)
73
- os.makedirs(settings.STATIC_ROOT, exist_ok=True)
73
+ os.makedirs(settings_module.GIT_ROOT, exist_ok=True)
74
+ os.makedirs(settings_module.JOBS_ROOT, exist_ok=True)
75
+ os.makedirs(settings_module.MEDIA_ROOT, exist_ok=True)
76
+ os.makedirs(os.path.join(settings_module.MEDIA_ROOT, "devicetype-images"), exist_ok=True)
77
+ os.makedirs(os.path.join(settings_module.MEDIA_ROOT, "image-attachments"), exist_ok=True)
78
+ os.makedirs(settings_module.STATIC_ROOT, exist_ok=True)
74
79
 
75
80
  #
76
81
  # Databases
@@ -78,62 +83,99 @@ def _preprocess_settings(settings, config_path):
78
83
 
79
84
  # If metrics are enabled and postgres is the backend, set the driver to the
80
85
  # one provided by django-prometheus.
81
- if settings.METRICS_ENABLED:
82
- if "postgres" in settings.DATABASES["default"]["ENGINE"]:
83
- settings.DATABASES["default"]["ENGINE"] = "django_prometheus.db.backends.postgresql"
84
- elif "mysql" in settings.DATABASES["default"]["ENGINE"]:
85
- settings.DATABASES["default"]["ENGINE"] = "django_prometheus.db.backends.mysql"
86
+ if settings_module.METRICS_ENABLED:
87
+ # Avoid modifying nautobot.core.settings.DATABASES by accident!
88
+ settings_module.DATABASES = deepcopy(settings_module.DATABASES)
89
+
90
+ if "postgres" in settings_module.DATABASES["default"]["ENGINE"]:
91
+ settings_module.DATABASES["default"]["ENGINE"] = "django_prometheus.db.backends.postgresql"
92
+ elif "mysql" in settings_module.DATABASES["default"]["ENGINE"]:
93
+ settings_module.DATABASES["default"]["ENGINE"] = "django_prometheus.db.backends.mysql"
86
94
 
87
95
  # Create secondary db connection for job logging. This still writes to the default db, but because it's a separate
88
96
  # connection, it allows allows us to "escape" from transaction.atomic() and ensure that job log entries are saved
89
97
  # to the database even when the rest of the job transaction is rolled back.
90
- settings.DATABASES["job_logs"] = settings.DATABASES["default"].copy()
98
+ settings_module.DATABASES["job_logs"] = deepcopy(settings_module.DATABASES["default"])
91
99
  # When running unit tests, treat it as a mirror of the default test DB, not a separate test DB of its own
92
- settings.DATABASES["job_logs"]["TEST"] = {"MIRROR": "default"}
100
+ settings_module.DATABASES["job_logs"]["TEST"] = {"MIRROR": "default"}
93
101
 
94
102
  #
95
103
  # Media storage
96
104
  #
97
105
 
98
- if hasattr(settings, "JOB_FILE_IO_STORAGE"):
99
- settings.STORAGES.setdefault("nautobotjobfiles", {})["BACKEND"] = settings.JOB_FILE_IO_STORAGE
106
+ # Avoid modifying nautobot.core.settings.STORAGES by accident!
107
+ settings_module.STORAGES = deepcopy(settings_module.STORAGES)
100
108
 
101
- if hasattr(settings, "STORAGE_BACKEND") and settings.STORAGE_BACKEND is not None:
102
- settings.STORAGES["default"]["BACKEND"] = settings.STORAGE_BACKEND
109
+ if hasattr(settings_module, "JOB_FILE_IO_STORAGE"):
110
+ settings_module.STORAGES.setdefault("nautobotjobfiles", {})["BACKEND"] = settings_module.JOB_FILE_IO_STORAGE
111
+
112
+ if hasattr(settings_module, "STORAGE_BACKEND") and settings_module.STORAGE_BACKEND is not None:
113
+ settings_module.STORAGES["default"]["BACKEND"] = settings_module.STORAGE_BACKEND
103
114
 
104
115
  # django-storages
105
- if hasattr(settings, "STORAGE_BACKEND") and settings.STORAGE_BACKEND.startswith("storages."):
116
+ if hasattr(settings_module, "STORAGE_BACKEND") and settings_module.STORAGE_BACKEND.startswith("storages."):
106
117
  try:
107
118
  import storages.utils
108
119
  except ModuleNotFoundError as e:
109
120
  if getattr(e, "name") == "storages":
110
121
  raise ImproperlyConfigured(
111
- f"STORAGE_BACKEND is set to {settings.STORAGE_BACKEND} but django-storages is not present. It "
112
- f"can be installed by running 'pip install django-storages'."
122
+ f"STORAGE_BACKEND is set to {settings_module.STORAGE_BACKEND} but django-storages is not present. "
123
+ "It can be installed by running 'pip install django-storages'."
113
124
  )
114
125
  raise e
115
126
 
116
127
  # Monkey-patch django-storages to fetch settings from STORAGE_CONFIG or fall back to settings
117
128
  def _setting(name, default=None):
118
- if name in settings.STORAGE_CONFIG:
119
- return settings.STORAGE_CONFIG[name]
120
- return getattr(settings, name, default)
129
+ if name in settings_module.STORAGE_CONFIG:
130
+ return settings_module.STORAGE_CONFIG[name]
131
+ return getattr(settings_module, name, default)
121
132
 
122
133
  storages.utils.setting = _setting
123
134
 
135
+ # Django 4.2 will throw an exception if both:
136
+ # - DEFAULT_FILE_STORAGE/STATICFILES_STORAGE is set in nautobot_config.py (recommended until Nautobot v2.4.24)
137
+ # - STORAGES is configured in nautobot.core.settings (which it is nowadays).
138
+ # Unfortunately, it's not implemented as a standard system check (which we could opt out of) but is instead
139
+ # hard-coded, so we hack around it instead by explicitly copying any non-default *_STORAGE to STORAGES
140
+ # and then unsetting *_STORAGE.
141
+ for setting_name, storages_key, default_value in [
142
+ ("DEFAULT_FILE_STORAGE", "default", "django.core.files.storage.FileSystemStorage"),
143
+ ("STATICFILES_STORAGE", "staticfiles", "django.contrib.staticfiles.storage.StaticFilesStorage"),
144
+ ]:
145
+ if hasattr(settings_module, setting_name):
146
+ # Make sure we don't clobber any existing explicit configuration in STORAGES:
147
+ if settings_module.STORAGES[storages_key]["BACKEND"] not in (
148
+ default_value, # Nautobot/Django default
149
+ getattr(settings_module, setting_name), # same as explicitly set value for setting_name
150
+ ):
151
+ raise ImproperlyConfigured(
152
+ f"It looks like you've configured both {setting_name} and STORAGES['{storages_key}']['BACKEND'],"
153
+ "but their values do not match."
154
+ )
155
+
156
+ # No clobbering, but undesired, so warn the user and handle it:
157
+ logger.warning(
158
+ f"It looks like you've configured {setting_name} in {settings_module.SETTINGS_PATH}. "
159
+ "This setting is deprecated since Nautobot v2.4.24, and support will be removed in Nautobot v3.1. "
160
+ f"You should migrate to configuring STORAGES['{storages_key}']['BACKEND'] instead. Refer to "
161
+ "https://docs.nautobot.com/projects/core/en/stable/user-guide/administration/configuration/settings/#storages for guidance."
162
+ )
163
+ settings_module.STORAGES[storages_key]["BACKEND"] = getattr(settings_module, setting_name)
164
+ delattr(settings_module, setting_name)
165
+
124
166
  #
125
167
  # Plugins
126
168
  #
127
169
 
128
170
  # Process the plugins and manipulate the specified config settings that are
129
171
  # passed in.
130
- load_plugins(settings)
172
+ load_plugins(settings_module)
131
173
 
132
174
  #
133
175
  # Event Broker
134
176
  #
135
177
 
136
- load_event_brokers(settings.EVENT_BROKERS)
178
+ load_event_brokers(settings_module.EVENT_BROKERS)
137
179
 
138
180
 
139
181
  def load_settings(config_path):
@@ -144,10 +186,10 @@ def load_settings(config_path):
144
186
  "Please provide a valid --config-path path, or use 'nautobot-server init' to create a new configuration."
145
187
  )
146
188
  spec = importlib.util.spec_from_file_location("nautobot_config", config_path)
147
- module = importlib.util.module_from_spec(spec)
148
- sys.modules["nautobot_config"] = module
149
- spec.loader.exec_module(module)
150
- _preprocess_settings(module, config_path)
189
+ settings_module = importlib.util.module_from_spec(spec)
190
+ sys.modules["nautobot_config"] = settings_module
191
+ spec.loader.exec_module(settings_module)
192
+ _preprocess_settings(settings_module, config_path)
151
193
 
152
194
 
153
195
  class _VerboseHelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
@@ -1886,8 +1886,8 @@ properties:
1886
1886
  For an example of using `django-storages` with AWS S3 buckets, visit the
1887
1887
  [django-storages with S3](../guides/s3-django-storage.md) user-guide.
1888
1888
 
1889
- The configuration parameters for the specified storage backend are defined under the
1890
- [`STORAGE_CONFIG`](#storage_config) setting.
1889
+ The configuration parameters for the specified storage backend are defined under the settings
1890
+ [`STORAGE_CONFIG`](#storage_config) (deprecated) or [`STORAGES`](#storages) (recommended).
1891
1891
  see_also:
1892
1892
  "`STORAGES`": "#storages"
1893
1893
  type: "string"
@@ -1895,8 +1895,16 @@ properties:
1895
1895
  description: "(Deprecated) Dictionary of config parameters for the storage backend configured as STORAGE_BACKEND."
1896
1896
  details: |-
1897
1897
  !!! warning
1898
- This setting is deprecated and will be removed in Nautobot v3.1.
1899
- In its place, you should set [`STORAGES["default"]["OPTIONS"]`](#storages).
1898
+ This setting is deprecated and will be removed in Nautobot v3.1. In its place, you should set
1899
+ [`STORAGES["default"]["OPTIONS"]` and/or `STORAGES["staticfiles"]["OPTIONS"]`](#storages).
1900
+
1901
+ Note that `STORAGE_CONFIG` is implemented to provide a bit of configuration "magic" that
1902
+ `STORAGES["..."]["OPTIONS"]` does not; specifically, when `STORAGE_BACKEND` is using any module from
1903
+ `django-storages`, if `STATICFILES_STORAGE` is also using `django-storages`, the `STORAGE_CONFIG` will be
1904
+ automatically applied to both file storage types. When using `STORAGES` instead of `STORAGE_CONFIG`, this is
1905
+ not automatically the case, permitting the two types to be configured independently, but also potentially
1906
+ requiring duplicate configuration under `STORAGES["default"]["OPTIONS"]` and
1907
+ `STORAGES["staticfiles"]["OPTIONS"]` if both types are using the same backend.
1900
1908
 
1901
1909
  The specific parameters to be used here are specific to each backend.
1902
1910
 
nautobot/core/tables.py CHANGED
@@ -9,7 +9,7 @@ from django.db.models import Prefetch, QuerySet
9
9
  from django.db.models.fields.related import ForeignKey, RelatedField
10
10
  from django.db.models.fields.reverse_related import ManyToOneRel
11
11
  from django.urls import reverse
12
- from django.utils.html import escape, format_html, format_html_join
12
+ from django.utils.html import format_html, format_html_join
13
13
  from django.utils.http import urlencode
14
14
  from django.utils.safestring import mark_safe
15
15
  from django.utils.text import Truncator
@@ -36,6 +36,7 @@ class BaseTable(django_tables2.Table):
36
36
  attrs = {
37
37
  "class": "table table-hover nb-table-headings",
38
38
  }
39
+ default = helpers.HTML_NONE
39
40
 
40
41
  def __init__(
41
42
  self,
@@ -579,7 +580,13 @@ class ColoredLabelColumn(django_tables2.TemplateColumn):
579
580
 
580
581
  template_code = """
581
582
  {% load helpers %}
582
- {% if value %}<span class="badge" style="color: {{ value.color|fgcolor }}; background-color: #{{ value.color }}">{{ value }}</span>{% else %}&mdash;{% endif %}
583
+ {% if value %}
584
+ <span class="badge" style="color: {{ value.color|fgcolor }}; background-color: #{{ value.color }}">
585
+ {{ value }}
586
+ </span>
587
+ {% else %}
588
+ <span class="text-secondary">&mdash;</span>
589
+ {% endif %}
583
590
  """
584
591
 
585
592
  def __init__(self, *args, **kwargs):
@@ -757,29 +764,33 @@ class CustomFieldColumn(django_tables2.Column):
757
764
  Display custom fields in the appropriate format.
758
765
  """
759
766
 
760
- # Add [] to empty_values so when there is no choice populated for multiselect_cf i.e. [], "—" is returned automatically.
761
- empty_values = (None, "", [])
762
-
763
767
  def __init__(self, customfield, *args, **kwargs):
764
768
  self.customfield = customfield
765
769
  kwargs["accessor"] = Accessor(f"_custom_field_data__{customfield.key}")
766
770
  kwargs["verbose_name"] = customfield.label
771
+ if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT:
772
+ # Add [] to empty_values so when there is no choice populated i.e. [], "—" is returned automatically.
773
+ kwargs.setdefault("empty_values", (None, "", []))
767
774
 
768
775
  super().__init__(*args, **kwargs)
769
776
 
770
777
  def render(self, *, record, bound_column, value): # pylint: disable=arguments-differ # tables2 varies its kwargs
771
- if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_BOOLEAN:
772
- template = helpers.render_boolean(value)
773
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT:
774
- template = format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
775
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_SELECT:
776
- template = format_html('<span class="badge bg-secondary">{}</span>', value)
777
- elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_URL:
778
- template = format_html('<a href="{}">{}</a>', value, value)
779
- else:
780
- template = escape(value)
778
+ # TODO: this logic could be unified with _ObjectCustomFieldsPanel.render_value
779
+ if self.customfield.type == choices.CustomFieldTypeChoices.TYPE_BOOLEAN and value is not None:
780
+ value = helpers.render_boolean(value)
781
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_JSON and value is not None:
782
+ value = helpers.render_json(value, pretty_print=True)
783
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MARKDOWN and value is not None:
784
+ value = helpers.render_markdown(value)
785
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_MULTISELECT and value:
786
+ value = format_html_join(" ", '<span class="badge bg-secondary">{}</span>', ((v,) for v in value))
787
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_SELECT and value is not None:
788
+ value = format_html('<span class="badge bg-secondary">{}</span>', value)
789
+ elif self.customfield.type == choices.CustomFieldTypeChoices.TYPE_URL and value:
790
+ value = format_html('<a href="{}">{}</a>', value, value)
791
+ # else (TEXT, INTEGER, DATE) or None value -- no need to do special rendering
781
792
 
782
- return template
793
+ return value
783
794
 
784
795
 
785
796
  class RelationshipColumn(django_tables2.Column):
@@ -811,7 +822,7 @@ class RelationshipColumn(django_tables2.Column):
811
822
  # Handle Symmetric Relationships
812
823
  # List `value` could be empty here [] after the filtering from above
813
824
  if len(value) < 1:
814
- return "—"
825
+ return helpers.HTML_NONE
815
826
 
816
827
  v = value[0]
817
828
  peer = v.get_peer(record)
@@ -81,11 +81,9 @@ add "&raw" to the end of the URL within a browser.
81
81
  <!-- As Nautobot may be run without internet access, we source these files locally rather than from an online CDN -->
82
82
  <link rel="stylesheet"
83
83
  href="{% static 'dist/css/graphql-libraries.css' %}"
84
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/graphql-libraries.css'">
85
- <script src="{% static 'dist/js/graphql-libraries.js' %}"
86
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/graphiql-libraries.js'"></script>
87
- <script src="{% static 'dist/js/nautobot-graphiql.js' %}"
88
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/nautobot-graphiql.js'"></script>
84
+ onerror="nb.media.handleFailure(this)">
85
+ <script src="{% static 'dist/js/graphql-libraries.js' %}" onerror="nb.media.handleFailure(this)"></script>
86
+ <script src="{% static 'dist/js/nautobot-graphiql.js' %}" onerror="nb.media.handleFailure(this)"></script>
89
87
  </head>
90
88
  <body>
91
89
  <!-- Nautobot page contents -->
@@ -2,16 +2,11 @@
2
2
  {% load plugins %}
3
3
  {% load static %}
4
4
 
5
- <script src="{% static 'dist/js/nautobot.js' %}"
6
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/nautobot.js'"></script>
7
- <script src="{% static 'dist/js/libraries.js' %}"
8
- onerror="window.location='{% url 'media_failure' %}?filename=dist/js/libraries.js'"></script>
9
- <script src="{% versioned_static 'js/forms.js' %}"
10
- onerror="window.location='{% url 'media_failure' %}?filename=js/forms.js'"></script>
11
- <script src="{% versioned_static 'js/table_sorting_indicator.js' %}"
12
- onerror="window.location='{% url 'media_failure' %}?filename=js/table_sorting_indicator.js'"></script>
13
- <script src="{% versioned_static 'js/dropdown.js' %}"
14
- onerror="window.location='{% url 'media_failure' %}?filename=js/dropdown.js'"></script>
5
+ <script src="{% static 'dist/js/nautobot.js' %}" onerror="nb.media.handleFailure(this)"></script>
6
+ <script src="{% static 'dist/js/libraries.js' %}" onerror="nb.media.handleFailure(this)"></script>
7
+ <script src="{% versioned_static 'js/forms.js' %}" onerror="nb.media.handleFailure(this)"></script>
8
+ <script src="{% versioned_static 'js/table_sorting_indicator.js' %}" onerror="nb.media.handleFailure(this)"></script>
9
+ <script src="{% versioned_static 'js/dropdown.js' %}" onerror="nb.media.handleFailure(this)"></script>
15
10
  <script type="text/javascript">
16
11
  var nautobot_static_url = "{% static '' %}";
17
12
  var nautobot_api_path = "{% url 'api-root' %}";
@@ -1,25 +1,26 @@
1
1
  {% load static %}
2
2
  {% load helpers %}
3
3
  {% load plugins %}
4
+ {% include 'inc/media_failure.html' %}
4
5
  <link rel="stylesheet" id="template-theme"
5
6
  href="{% url 'template_css' %}">
6
7
  <link rel="stylesheet"
7
8
  href="{% static 'dist/css/nautobot.css' %}"
8
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/nautobot.css'">
9
+ onerror="nb.media.handleFailure(this)">
9
10
  <link rel="stylesheet"
10
11
  href="{% static 'dist/css/materialdesignicons.css' %}"
11
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/materialdesignicons.css'">
12
+ onerror="nb.media.handleFailure(this)">
12
13
  {% with cookie_theme=request.COOKIES|get_item:'theme' %}
13
14
  {% if not cookie_theme or cookie_theme == 'light' %}
14
15
  <link rel="stylesheet"
15
16
  href="{% static 'dist/css/github.min.css' %}"
16
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/github.min.css'"
17
+ onerror="nb.media.handleFailure(this)"
17
18
  {% if not cookie_theme %}media="not (prefers-color-scheme: dark)"{% endif %}>
18
19
  {% endif %}
19
20
  {% if not cookie_theme or cookie_theme == 'dark' %}
20
21
  <link rel="stylesheet"
21
22
  href="{% static 'dist/css/github-dark.min.css' %}"
22
- onerror="window.location='{% url 'media_failure' %}?filename=dist/css/github-dark.min.css'"
23
+ onerror="nb.media.handleFailure(this)"
23
24
  {% if not cookie_theme %}media="(prefers-color-scheme: dark)"{% endif %}>
24
25
  {% endif %}
25
26
  {% endwith %}
@@ -0,0 +1,73 @@
1
+ {% load static %}
2
+
3
+ <script>
4
+ (function initMediaFailure() {
5
+ // Create React-style `ref` reference to the media failure alert element.
6
+ const failureAlertRef = { current: null };
7
+
8
+ // Store all media failures inside `Map` containing `Set` values to ensure uniqueness of all reported failures.
9
+ const failures = new Map();
10
+
11
+ function handleFailure(element) {
12
+ const url = element.href || element.src || '';
13
+
14
+ if (url) {
15
+ // Update `failures` with a proper entry corresponding to the current media failure and update media failure alert.
16
+ const elements = failures.get(url) || new Set();
17
+ elements.add(element);
18
+ failures.set(url, elements);
19
+ updateFailureAlert();
20
+ }
21
+ }
22
+
23
+ function updateFailureAlert() {
24
+ const count = Array.from(failures.values()).reduce((count, elements) => count + elements.size, 0);
25
+
26
+ const innerHTML = `
27
+ <div style="margin: auto; max-width: 50rem;">
28
+ <h2 style="margin-top: 0; text-align: center;">Static Media Failure</h2>
29
+ <p><strong>${count}</strong> static media file${count > 1 ? 's' : ''} failed to load.</p>
30
+ <p style="margin-bottom: 0;">
31
+ Check whether
32
+ <code><strong>nautobot-server collectstatic</strong></code>
33
+ was run during the most recent upgrade and that the HTTP service (e.g. NGINX) is configured to serve static files.
34
+ Refer to
35
+ <a href="https://docs.nautobot.com/projects/core/en/v{{ settings.VERSION }}/installation/http-server/#static-media-failure" target="_blank">
36
+ the installation documentation
37
+ </a>
38
+ for further guidance.
39
+ </p>
40
+ </div>
41
+ `;
42
+
43
+ // If media failure alert already exists, just update its content and return early.
44
+ if (failureAlertRef.current) {
45
+ failureAlertRef.current.innerHTML = innerHTML;
46
+ return;
47
+ }
48
+
49
+ // If media failure alert does not exist yet, create one.
50
+ const alert = document.createElement('div');
51
+ alert.classList.add('alert', 'alert-danger', 'media-failure-banner');
52
+ alert.setAttribute('role', 'alert');
53
+ alert.innerHTML = innerHTML;
54
+
55
+ // Copy and hardcode Bootstrap `"alert alert-danger"` styles here to make the alert core styles independent of other CSS.
56
+ alert.style.setProperty('background-color', 'var(--bs-alert-bg, var(--bs-danger-bg-subtle, #fce8e8))');
57
+ alert.style.setProperty('border', 'var(--bs-alert-border, var(--bs-border-width, 0.0625rem) solid var(--bs-alert-border-color, var(--bs-danger-border-subtle, #f9d2d2)))');
58
+ alert.style.setProperty('border-radius', '0');
59
+ alert.style.setProperty('color', 'var(--bs-alert-color, var(--bs-danger-text-emphasis, #e01f1f))');
60
+ alert.style.setProperty('grid-area', 'banner-global-area');
61
+ alert.style.setProperty('padding', 'var(--bs-alert-padding-y, 1rem) 1.25rem');
62
+ alert.style.setProperty('position', 'relative');
63
+
64
+ // Update React-style `ref` to simplify later alert updates if required and place the alert as the first child of document body.
65
+ failureAlertRef.current = alert;
66
+ document.body.prepend(alert);
67
+ }
68
+
69
+ // Export `media` "API" to `window` global object to enable accessing it from other scripts.
70
+ window.nb = window.nb || {};
71
+ window.nb.media = { failures, handleFailure }
72
+ })();
73
+ </script>
@@ -1,3 +1,4 @@
1
+ {% comment %}4.0 TODO: remove this template, which only exists for backward compatibility with 3.0 and earlier{% endcomment %}
1
2
  {% load static %}
2
3
  <!DOCTYPE html>
3
4
  <html lang="en">
@@ -1,4 +1,9 @@
1
- from nautobot.core.cli import migrate_deprecated_templates
1
+ import importlib.util
2
+ import os.path
3
+ import sys
4
+ from unittest import mock
5
+
6
+ from nautobot.core.cli import _preprocess_settings, migrate_deprecated_templates
2
7
  from nautobot.core.testing import TestCase
3
8
 
4
9
 
@@ -38,3 +43,117 @@ class TestMigrateTemplates(TestCase):
38
43
  replaced_content, was_updated = migrate_deprecated_templates.replace_template_references(original_content)
39
44
  self.assertTrue(was_updated)
40
45
  self.assertEqual(replaced_content, new_content)
46
+
47
+
48
+ @mock.patch("nautobot.core.cli.load_plugins")
49
+ @mock.patch("nautobot.core.cli.load_event_brokers")
50
+ class TestPreprocessSettings(TestCase):
51
+ """Tests for the `_preprocess_settings` function in nautobot.core.cli, as it's important to Nautobot startup."""
52
+
53
+ def load_settings_module(self):
54
+ # Load the testing nautobot_config.py as a self-contained module
55
+ config_path = os.path.join(os.path.dirname(__file__), "nautobot_config.py")
56
+ spec = importlib.util.spec_from_file_location("test_nautobot_config", config_path)
57
+ settings_module = importlib.util.module_from_spec(spec)
58
+ # nautobot.core.cli.load_settings would do the below, but obviously we don't want to do that here:
59
+ # sys.modules["nautobot_config"] = settings_module
60
+ spec.loader.exec_module(settings_module)
61
+ return settings_module, config_path
62
+
63
+ def test_basic_path(self, mock_load_event_brokers, mock_load_plugins):
64
+ """Basic operation of the function."""
65
+ settings_module, config_path = self.load_settings_module()
66
+
67
+ # Process the settings module
68
+ _preprocess_settings(settings_module, config_path)
69
+
70
+ # _preprocess_settings should have set SETTINGS_PATH on the module
71
+ self.assertEqual(settings_module.SETTINGS_PATH, config_path)
72
+
73
+ # the default test settings have no EXTRA_* settings to handle
74
+
75
+ # all media paths should exist
76
+ self.assertTrue(os.path.isdir(settings_module.GIT_ROOT))
77
+ self.assertTrue(os.path.isdir(settings_module.JOBS_ROOT))
78
+ self.assertTrue(os.path.isdir(settings_module.MEDIA_ROOT))
79
+ self.assertTrue(os.path.isdir(os.path.join(settings_module.MEDIA_ROOT, "devicetype-images")))
80
+ self.assertTrue(os.path.isdir(os.path.join(settings_module.MEDIA_ROOT, "image-attachments")))
81
+ self.assertTrue(os.path.isdir(settings_module.STATIC_ROOT))
82
+
83
+ # databases should be using the prometheus backends
84
+ self.assertTrue(settings_module.METRICS_ENABLED)
85
+ self.assertIn("django_prometheus.db.backends", settings_module.DATABASES["default"]["ENGINE"])
86
+
87
+ # job_logs database connection should exist
88
+ self.assertIn("job_logs", settings_module.DATABASES)
89
+ self.assertIn("TEST", settings_module.DATABASES["job_logs"])
90
+ self.assertEqual(settings_module.DATABASES["job_logs"]["TEST"], {"MIRROR": "default"})
91
+ for key, value in settings_module.DATABASES["default"].items():
92
+ if key == "TEST":
93
+ continue
94
+ self.assertEqual(value, settings_module.DATABASES["job_logs"][key])
95
+
96
+ # STORAGES should remain as default
97
+ self.assertEqual(
98
+ settings_module.STORAGES,
99
+ {
100
+ "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
101
+ "staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
102
+ "nautobotjobfiles": {"BACKEND": "db_file_storage.storage.DatabaseFileStorage"},
103
+ },
104
+ )
105
+
106
+ mock_load_plugins.assert_called_with(settings_module)
107
+ mock_load_event_brokers.assert_called_with(settings_module.EVENT_BROKERS)
108
+
109
+ def test_EXTRA_behavior(self, *args):
110
+ """Handling of special settings like EXTRA_INSTALLED_APPS and EXTRA_MIDDLEWARE."""
111
+ settings_module, config_path = self.load_settings_module()
112
+
113
+ # Inject EXTRA_INSTALLED_APPS and EXTRA_MIDDLEWARE to the settings module for test purposes
114
+ settings_module.EXTRA_INSTALLED_APPS = ["foo.bar"]
115
+ settings_module.EXTRA_MIDDLEWARE = ("baz.bat",)
116
+
117
+ # Process the settings module
118
+ _preprocess_settings(settings_module, config_path)
119
+
120
+ self.assertIn("foo.bar", settings_module.INSTALLED_APPS)
121
+ # more specifically:
122
+ self.assertEqual("foo.bar", settings_module.INSTALLED_APPS[-1])
123
+ self.assertIn("baz.bat", settings_module.MIDDLEWARE)
124
+ # more specifically:
125
+ self.assertEqual("baz.bat", settings_module.MIDDLEWARE[-1])
126
+
127
+ def test_legacy_storage_behavior(self, *args):
128
+ """Handling legacy storage settings."""
129
+ settings_module, config_path = self.load_settings_module()
130
+
131
+ settings_module.DEFAULT_FILE_STORAGE = "storages.some_custom_backend"
132
+ settings_module.JOB_FILE_IO_STORAGE = "some_custom_job_file_storage"
133
+ settings_module.STATICFILES_STORAGE = "some_custom_static_storage"
134
+ settings_module.STORAGE_BACKEND = "storages.some_custom_backend"
135
+ settings_module.STORAGE_CONFIG = {"MY_BACKEND_OPTION": "some_value"}
136
+
137
+ import storages.utils
138
+
139
+ original_setting = storages.utils.setting
140
+ del sys.modules["storages.utils"]
141
+ del storages.utils
142
+
143
+ try:
144
+ # Process the settings module
145
+ _preprocess_settings(settings_module, config_path)
146
+
147
+ self.assertFalse(hasattr(settings_module, "DEFAULT_FILE_STORAGE")) # unset to avoid a Django exception
148
+ self.assertEqual("storages.some_custom_backend", settings_module.STORAGES["default"]["BACKEND"])
149
+ self.assertEqual("some_custom_job_file_storage", settings_module.STORAGES["nautobotjobfiles"]["BACKEND"])
150
+ self.assertFalse(hasattr(settings_module, "STATICFILES_STORAGE")) # unset to avoid a Django exception
151
+ self.assertEqual("some_custom_static_storage", settings_module.STORAGES["staticfiles"]["BACKEND"])
152
+
153
+ self.assertEqual("some_value", storages.utils.setting("MY_BACKEND_OPTION"))
154
+ finally:
155
+ # Clean up the STORAGE_CONFIG monkeypatch
156
+ import storages.utils # pylint: disable=reimported
157
+
158
+ storages.utils.setting = original_setting
159
+ self.assertIsNone(storages.utils.setting("MY_BACKEND_OPTION"))