nautobot 2.3.3__py3-none-any.whl → 2.3.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.

Potentially problematic release.


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

Files changed (333) hide show
  1. nautobot/core/celery/schedulers.py +2 -2
  2. nautobot/core/settings.py +3 -1
  3. nautobot/core/settings.yaml +40 -23
  4. nautobot/core/tests/runner.py +13 -6
  5. nautobot/core/tests/test_settings_schema.py +7 -3
  6. nautobot/core/tests/test_views.py +40 -1
  7. nautobot/core/views/generic.py +15 -15
  8. nautobot/core/views/mixins.py +12 -1
  9. nautobot/core/views/renderers.py +3 -1
  10. nautobot/core/views/utils.py +1 -1
  11. nautobot/dcim/api/serializers.py +1 -0
  12. nautobot/dcim/api/views.py +2 -0
  13. nautobot/dcim/forms.py +1 -1
  14. nautobot/dcim/tables/power.py +1 -1
  15. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +1 -1
  16. nautobot/dcim/tests/test_api.py +58 -4
  17. nautobot/dcim/tests/test_filters.py +1 -1
  18. nautobot/dcim/tests/test_models.py +0 -2
  19. nautobot/dcim/views.py +5 -2
  20. nautobot/extras/api/views.py +9 -0
  21. nautobot/extras/models/jobs.py +9 -1
  22. nautobot/extras/querysets.py +10 -1
  23. nautobot/extras/tables.py +3 -0
  24. nautobot/extras/tests/test_api.py +36 -0
  25. nautobot/extras/tests/test_views.py +76 -1
  26. nautobot/extras/views.py +10 -7
  27. nautobot/ipam/forms.py +6 -1
  28. nautobot/ipam/models.py +5 -11
  29. nautobot/ipam/navigation.py +8 -1
  30. nautobot/ipam/templates/ipam/prefix.html +1 -1
  31. nautobot/ipam/tests/test_filters.py +1 -1
  32. nautobot/ipam/tests/test_views.py +41 -41
  33. nautobot/ipam/views.py +1 -1
  34. nautobot/project-static/docs/404.html +60 -148
  35. nautobot/project-static/docs/additional-features/caching.html +3 -3
  36. nautobot/project-static/docs/additional-features/healthcheck.html +3 -3
  37. nautobot/project-static/docs/apps/index.html +60 -148
  38. nautobot/project-static/docs/apps/nautobot-apps.html +60 -148
  39. nautobot/project-static/docs/assets/javascripts/workers/{search.07f07601.min.js → search.6ce7567c.min.js} +3 -3
  40. nautobot/project-static/docs/assets/javascripts/workers/{search.07f07601.min.js.map → search.6ce7567c.min.js.map} +2 -2
  41. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +60 -148
  42. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +60 -148
  43. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +60 -148
  44. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +60 -148
  45. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +60 -148
  46. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +60 -148
  47. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +60 -148
  48. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +60 -148
  49. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +60 -148
  50. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +60 -148
  51. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +60 -148
  52. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +60 -148
  53. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +60 -148
  54. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +60 -148
  55. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +60 -148
  56. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -148
  57. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +60 -148
  58. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +60 -148
  59. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +60 -148
  60. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +60 -148
  61. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +60 -148
  62. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +60 -148
  63. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +60 -148
  64. nautobot/project-static/docs/configuration/optional-settings.html +3 -3
  65. nautobot/project-static/docs/configuration/required-settings.html +3 -3
  66. nautobot/project-static/docs/development/apps/api/configuration-view.html +60 -148
  67. nautobot/project-static/docs/development/apps/api/database-backend-config.html +60 -148
  68. nautobot/project-static/docs/development/apps/api/models/django-admin.html +60 -148
  69. nautobot/project-static/docs/development/apps/api/models/global-search.html +60 -148
  70. nautobot/project-static/docs/development/apps/api/models/graphql.html +60 -148
  71. nautobot/project-static/docs/development/apps/api/models/index.html +60 -148
  72. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +60 -148
  73. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +60 -148
  74. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +60 -148
  75. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +60 -148
  76. nautobot/project-static/docs/development/apps/api/platform-features/index.html +60 -148
  77. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +60 -148
  78. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +60 -148
  79. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +60 -148
  80. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +60 -148
  81. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +62 -150
  82. nautobot/project-static/docs/development/apps/api/prometheus.html +60 -148
  83. nautobot/project-static/docs/development/apps/api/setup.html +60 -148
  84. nautobot/project-static/docs/development/apps/api/testing.html +61 -149
  85. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +60 -148
  86. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +60 -148
  87. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +60 -148
  88. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +60 -148
  89. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +60 -148
  90. nautobot/project-static/docs/development/apps/api/views/base-template.html +60 -148
  91. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +60 -148
  92. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +60 -148
  93. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +60 -148
  94. nautobot/project-static/docs/development/apps/api/views/index.html +60 -148
  95. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +60 -148
  96. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +60 -148
  97. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +60 -148
  98. nautobot/project-static/docs/development/apps/api/views/notes.html +60 -148
  99. nautobot/project-static/docs/development/apps/api/views/rest-api.html +60 -148
  100. nautobot/project-static/docs/development/apps/api/views/urls.html +60 -148
  101. nautobot/project-static/docs/development/apps/index.html +61 -149
  102. nautobot/project-static/docs/development/apps/migration/code-updates.html +60 -148
  103. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +60 -148
  104. nautobot/project-static/docs/development/apps/migration/from-v1.html +60 -148
  105. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +60 -148
  106. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +60 -148
  107. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +60 -148
  108. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +60 -148
  109. nautobot/project-static/docs/development/apps/porting-from-netbox.html +60 -148
  110. nautobot/project-static/docs/development/core/application-registry.html +60 -148
  111. nautobot/project-static/docs/development/core/best-practices.html +60 -148
  112. nautobot/project-static/docs/development/core/bootstrap-ui.html +60 -148
  113. nautobot/project-static/docs/development/core/caching.html +60 -148
  114. nautobot/project-static/docs/development/core/controllers.html +60 -148
  115. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +60 -148
  116. nautobot/project-static/docs/development/core/generic-views.html +60 -148
  117. nautobot/project-static/docs/development/core/getting-started.html +85 -169
  118. nautobot/project-static/docs/development/core/homepage.html +60 -148
  119. nautobot/project-static/docs/development/core/index.html +60 -148
  120. nautobot/project-static/docs/development/core/model-checklist.html +60 -148
  121. nautobot/project-static/docs/development/core/model-features.html +60 -148
  122. nautobot/project-static/docs/development/core/natural-keys.html +60 -148
  123. nautobot/project-static/docs/development/core/navigation-menu.html +60 -148
  124. nautobot/project-static/docs/development/core/release-checklist.html +60 -148
  125. nautobot/project-static/docs/development/core/role-internals.html +60 -148
  126. nautobot/project-static/docs/development/core/settings.html +60 -148
  127. nautobot/project-static/docs/development/core/style-guide.html +60 -148
  128. nautobot/project-static/docs/development/core/templates.html +60 -148
  129. nautobot/project-static/docs/development/core/testing.html +72 -152
  130. nautobot/project-static/docs/development/core/user-preferences.html +60 -148
  131. nautobot/project-static/docs/development/index.html +60 -148
  132. nautobot/project-static/docs/development/jobs/index.html +68 -156
  133. nautobot/project-static/docs/development/jobs/migration/from-v1.html +61 -149
  134. nautobot/project-static/docs/docker/index.html +3 -3
  135. nautobot/project-static/docs/index.html +60 -148
  136. nautobot/project-static/docs/installation/selinux-troubleshooting.html +3 -3
  137. nautobot/project-static/docs/overview/application_stack.html +60 -148
  138. nautobot/project-static/docs/overview/design_philosophy.html +60 -148
  139. nautobot/project-static/docs/release-notes/index.html +60 -148
  140. nautobot/project-static/docs/release-notes/version-1.0.html +60 -148
  141. nautobot/project-static/docs/release-notes/version-1.1.html +61 -149
  142. nautobot/project-static/docs/release-notes/version-1.2.html +63 -151
  143. nautobot/project-static/docs/release-notes/version-1.3.html +61 -149
  144. nautobot/project-static/docs/release-notes/version-1.4.html +62 -150
  145. nautobot/project-static/docs/release-notes/version-1.5.html +62 -150
  146. nautobot/project-static/docs/release-notes/version-1.6.html +64 -152
  147. nautobot/project-static/docs/release-notes/version-2.0.html +61 -149
  148. nautobot/project-static/docs/release-notes/version-2.1.html +62 -150
  149. nautobot/project-static/docs/release-notes/version-2.2.html +60 -148
  150. nautobot/project-static/docs/release-notes/version-2.3.html +475 -236
  151. nautobot/project-static/docs/search/search_index.json +1 -1
  152. nautobot/project-static/docs/sitemap.xml +277 -285
  153. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  154. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +62 -150
  155. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +61 -149
  156. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +62 -150
  157. nautobot/project-static/docs/user-guide/administration/configuration/index.html +71 -169
  158. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +13 -12524
  159. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +8966 -0
  160. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +13 -9218
  161. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +12734 -0
  162. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -152
  163. nautobot/project-static/docs/user-guide/administration/guides/caching.html +13 -9108
  164. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +61 -149
  165. nautobot/project-static/docs/user-guide/administration/guides/docker.html +9491 -0
  166. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9478 -0
  167. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +13 -8833
  168. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -152
  169. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +61 -149
  170. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +61 -149
  171. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +60 -148
  172. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +60 -148
  173. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +8978 -0
  174. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +60 -148
  175. nautobot/project-static/docs/user-guide/administration/installation/docker.html +3 -3
  176. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +63 -151
  177. nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +3 -3
  178. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +61 -151
  179. nautobot/project-static/docs/user-guide/administration/installation/index.html +62 -150
  180. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -148
  181. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +76 -161
  182. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +3 -3
  183. nautobot/project-static/docs/user-guide/administration/installation/services.html +62 -150
  184. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +13 -9577
  185. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +13 -9560
  186. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +13 -9064
  187. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +62 -150
  188. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +60 -148
  189. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +66 -154
  190. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +63 -151
  191. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +66 -154
  192. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +63 -151
  193. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +63 -151
  194. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +63 -151
  195. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +63 -151
  196. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +63 -151
  197. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +63 -151
  198. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +65 -153
  199. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +63 -151
  200. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +60 -148
  201. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +60 -148
  202. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +60 -148
  203. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +60 -148
  204. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +60 -148
  205. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +60 -148
  206. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +60 -148
  207. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +60 -148
  208. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +60 -148
  209. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +60 -148
  210. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +60 -148
  211. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +60 -148
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +60 -148
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +60 -148
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +60 -148
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +60 -148
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +60 -148
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +60 -148
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +60 -148
  219. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +60 -148
  220. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +60 -148
  221. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +60 -148
  222. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +60 -148
  223. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +60 -148
  224. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +60 -148
  225. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +60 -148
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +60 -148
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +60 -148
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +60 -148
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +60 -148
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +60 -148
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +60 -148
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +60 -148
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +60 -148
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +60 -148
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +60 -148
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +60 -148
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +60 -148
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +61 -149
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +60 -148
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +60 -148
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +60 -148
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +60 -148
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +60 -148
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +60 -148
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +60 -148
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +60 -148
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +60 -148
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +60 -148
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +60 -148
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +60 -148
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +60 -148
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +60 -148
  253. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +61 -149
  254. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +60 -148
  255. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +60 -148
  256. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +60 -148
  257. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +60 -148
  258. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +60 -148
  259. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +60 -148
  260. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +60 -148
  261. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +60 -148
  262. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +60 -148
  263. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +60 -148
  264. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +60 -148
  265. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +60 -148
  266. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +60 -148
  267. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +60 -148
  268. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +60 -148
  269. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +60 -148
  270. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +60 -148
  271. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +60 -148
  272. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +60 -148
  273. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +60 -148
  274. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +60 -148
  275. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +60 -148
  276. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +60 -148
  277. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +60 -148
  278. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +60 -148
  279. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +60 -148
  280. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +60 -148
  281. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +60 -148
  282. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +60 -148
  283. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +60 -148
  284. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +60 -148
  285. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +61 -149
  286. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +60 -148
  287. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +60 -148
  288. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +60 -148
  289. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +60 -148
  290. nautobot/project-static/docs/user-guide/index.html +60 -148
  291. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +60 -148
  292. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +60 -148
  293. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +60 -148
  294. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +60 -148
  295. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +61 -149
  296. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +60 -148
  297. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +60 -148
  298. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +61 -149
  299. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +62 -150
  300. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +60 -148
  301. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +61 -149
  302. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +60 -148
  303. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +60 -148
  304. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +60 -148
  305. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +60 -148
  306. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +60 -148
  307. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +61 -149
  308. nautobot/project-static/docs/user-guide/platform-functionality/note.html +60 -148
  309. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +60 -148
  310. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +60 -148
  311. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +61 -149
  312. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +61 -149
  313. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +62 -150
  314. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +60 -148
  315. nautobot/project-static/docs/user-guide/platform-functionality/role.html +60 -148
  316. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +60 -148
  317. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +60 -148
  318. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +60 -148
  319. nautobot/project-static/docs/user-guide/platform-functionality/status.html +60 -148
  320. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +60 -148
  321. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +60 -148
  322. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +60 -148
  323. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +60 -148
  324. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +60 -148
  325. nautobot/virtualization/filters.py +6 -1
  326. nautobot/virtualization/tables.py +2 -2
  327. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/METADATA +2 -2
  328. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/RECORD +332 -328
  329. nautobot/project-static/docs/user-guide/administration/configuration/render-settings-fragment.j2 +0 -76
  330. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/LICENSE.txt +0 -0
  331. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/NOTICE +0 -0
  332. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/WHEEL +0 -0
  333. {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Mapping
2
- from datetime import datetime
2
+ from datetime import datetime, timedelta
3
3
  import logging
4
4
  from pathlib import Path
5
5
 
@@ -83,7 +83,7 @@ class NautobotScheduleEntry(ModelEntry):
83
83
  # This will trigger the job to run at start_time
84
84
  # and avoid the heap block.
85
85
  if model.start_time:
86
- model.last_run_at = model.last_run_at - datetime.timedelta(days=365 * 30)
86
+ model.last_run_at = model.last_run_at - timedelta(days=365 * 30)
87
87
 
88
88
  self.last_run_at = model.last_run_at
89
89
 
nautobot/core/settings.py CHANGED
@@ -641,6 +641,8 @@ LOGIN_REDIRECT_URL = "home"
641
641
  CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
642
642
  CONSTANCE_DATABASE_PREFIX = "constance:nautobot:"
643
643
  CONSTANCE_DATABASE_CACHE_BACKEND = "default"
644
+ # Constance defaults to a 24-hour timeout when autofilling its cache, which is undesirable in many cases.
645
+ CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT = int(os.getenv("NAUTOBOT_CACHES_TIMEOUT", "300"))
644
646
  CONSTANCE_IGNORE_ADMIN_VERSION_CHECK = True # avoid potential errors in a multi-node deployment
645
647
 
646
648
  CONSTANCE_ADDITIONAL_FIELDS = {
@@ -853,7 +855,7 @@ CACHES = {
853
855
  "django_prometheus.cache.backends.redis.RedisCache" if METRICS_ENABLED else "django_redis.cache.RedisCache",
854
856
  ),
855
857
  "LOCATION": parse_redis_connection(redis_database=1),
856
- "TIMEOUT": 300,
858
+ "TIMEOUT": int(os.getenv("NAUTOBOT_CACHES_TIMEOUT", "300")),
857
859
  "OPTIONS": {
858
860
  "CLIENT_CLASS": "django_redis.client.DefaultClient",
859
861
  "PASSWORD": "",
@@ -47,7 +47,7 @@ properties:
47
47
  description: >-
48
48
  A list of valid fully-qualified domain names (FQDNs) and/or IP addresses that can be used to reach the
49
49
  Nautobot service. (If provided as an environment variable, it should be a space-separated string, for example
50
- `NAUTOBOT_ALLOWED_HOSTS="localhost 127.0.0.1 example.com"`)
50
+ `NAUTOBOT_ALLOWED_HOSTS="localhost 127.0.0.1 nautobot.example.com"`)
51
51
  details: |-
52
52
  Usually this is the same as the hostname for the Nautobot server, but can also be different; for example,
53
53
  when using a reverse proxy serving the Nautobot website under a different FQDN than the hostname of the
@@ -70,7 +70,7 @@ properties:
70
70
 
71
71
  !!! tip
72
72
  If there is more than one hostname in this list, you may also need to set
73
- [CSRF_TRUSTED_ORIGINS](optional-settings.md#csrf_trusted_origins) as well.
73
+ [CSRF_TRUSTED_ORIGINS](settings.md#csrf_trusted_origins) as well.
74
74
 
75
75
  If you are not yet sure what the domain name and/or IP address of the Nautobot installation will be,
76
76
  and are comfortable accepting the risks in doing so, you can set this to a wildcard (asterisk) to
@@ -83,7 +83,6 @@ properties:
83
83
  !!! warning
84
84
  It is not recommended to leave this value as `['*']` for production deployments.
85
85
  environment_variable: "NAUTOBOT_ALLOWED_HOSTS"
86
- is_required_setting: true
87
86
  items:
88
87
  type: "string"
89
88
  see_also:
@@ -297,19 +296,21 @@ properties:
297
296
  TIMEOUT: 300
298
297
  description: "The `CACHES` setting is required to simplify the configuration for `django-redis`."
299
298
  details: |-
300
- The [`django-redis`](https://github.com/jazzband/django-redis) Django plugin is used to enable Redis
301
- as a concurrent write lock for preventing race conditions when allocating IP address objects.
299
+ The [`django-redis`](https://github.com/jazzband/django-redis) Django plugin is used to enable Redis as a
300
+ concurrent write lock for preventing race conditions when allocating IP address objects.
301
+ Nautobot also uses the built-in [Django cache framework](https://docs.djangoproject.com/en/stable/topics/cache/)
302
+ (which also relies on the `CACHES` setting) to perform caching. This includes caching of the values of
303
+ [administratively configurable settings](#administratively-configurable-settings) as stored in the database.
302
304
 
303
- !!! important
304
- Nautobot also utilizes the built-in
305
- [Django cache framework](https://docs.djangoproject.com/en/stable/topics/cache/)
306
- (which also relies on the `CACHES` setting) to perform caching.
307
-
308
- +/- 2.0.0
309
- The default value of `CACHES["default"]["LOCATION"]` has changed from `redis://localhost:6379/0`
310
- to `redis://localhost:6379/1`, as Django's native caching is now taking the role previously occupied by
311
- `django-cacheops`.
312
- is_required_setting: true
305
+ !!! tip
306
+ Rather than directly setting `CACHES["default"]["LOCATION"]`, we recommend managing this setting via
307
+ the various `NAUTOBOT_REDIS_*` environment variables, as those variables apply to both `CACHES` and
308
+ [`CELERY_BROKER_URL`](#celery_broker_url) alike, which is typically preferable.
309
+
310
+ +++ 2.3.4 "`NAUTOBOT_CACHES_TIMEOUT` environment variable"
311
+ Added support for the environment variable `NAUTOBOT_CACHES_TIMEOUT` for configuring the
312
+ `CACHES["default"]["TIMEOUT"]` setting. This environment variable also controls the cache timeout
313
+ for administratively configurable settings.
313
314
  properties:
314
315
  default:
315
316
  properties:
@@ -322,6 +323,13 @@ properties:
322
323
  type: "string"
323
324
  LOCATION:
324
325
  default: "redis://localhost:6379/1"
326
+ environment_variables:
327
+ - "NAUTOBOT_REDIS_SCHEME"
328
+ - "NAUTOBOT_REDIS_SSL"
329
+ - "NAUTOBOT_REDIS_USERNAME"
330
+ - "NAUTOBOT_REDIS_PASSWORD"
331
+ - "NAUTOBOT_REDIS_HOST"
332
+ - "NAUTOBOT_REDIS_PORT"
325
333
  format: "uri"
326
334
  type: "string"
327
335
  OPTIONS:
@@ -336,10 +344,11 @@ properties:
336
344
  type: "object"
337
345
  TIMEOUT:
338
346
  default: 300
347
+ environment_variable: "NAUTOBOT_CACHES_TIMEOUT"
339
348
  type: "integer"
340
349
  type: "object"
341
350
  see_also:
342
- "Guide to Nautobot Caching, including TLS and HA configuration": "../../administration/guides/caching.md"
351
+ "Guide to Nautobot Redis configuration, including TLS and HA configuration": "redis.md"
343
352
  type: "object"
344
353
  CELERY_BEAT_HEARTBEAT_FILE:
345
354
  default: "/tmp/nautobot_celery_beat_heartbeat"
@@ -351,8 +360,7 @@ properties:
351
360
  default: {}
352
361
  description: "A dict of additional options passed to the Celery broker transport."
353
362
  details: >-
354
- This is only required when
355
- [configuring Celery to utilize Redis Sentinel](../../administration/guides/caching.md#celery-sentinel-configuration).
363
+ This is only required when [configuring Celery to utilize Redis Sentinel](redis.md#celery-sentinel-configuration).
356
364
  properties:
357
365
  master_name:
358
366
  type: "string"
@@ -366,7 +374,18 @@ properties:
366
374
  CELERY_BROKER_URL:
367
375
  default: "redis://localhost:6379/0"
368
376
  description: "Celery broker URL used to tell workers where queues are located."
369
- environment_variable: "NAUTOBOT_CELERY_BROKER_URL"
377
+ details: >-
378
+ If the `NAUTOBOT_CELERY_BROKER_URL` environment variable is not set, the default for this setting will be
379
+ influenced by the various `NAUTOBOT_REDIS_*` environment variables instead, which is often preferable as those
380
+ variables also influence the [`CACHES`](#caches) configuration as well.
381
+ environment_variables:
382
+ - "NAUTOBOT_CELERY_BROKER_URL"
383
+ - "NAUTOBOT_REDIS_SCHEME"
384
+ - "NAUTOBOT_REDIS_SSL"
385
+ - "NAUTOBOT_REDIS_USERNAME"
386
+ - "NAUTOBOT_REDIS_PASSWORD"
387
+ - "NAUTOBOT_REDIS_HOST"
388
+ - "NAUTOBOT_REDIS_PORT"
370
389
  format: "uri"
371
390
  type: "string"
372
391
  CELERY_BROKER_USE_SSL:
@@ -628,7 +647,6 @@ properties:
628
647
 
629
648
  As of Nautobot 1.1.5 and later, if you have generated a new `nautobot_config.py` using
630
649
  `nautobot-server init`, this line is already present in your config and no action is required.
631
- is_required_setting: true
632
650
  properties:
633
651
  default:
634
652
  additionalProperties: true
@@ -828,7 +846,7 @@ properties:
828
846
  FORCE_SCRIPT_NAME:
829
847
  default: null
830
848
  description: >-
831
- If not None, this will be used as the value of the SCRIPT_NAME environment variable in any HTTP request.
849
+ If not None, this will be used as the value of the `SCRIPT_NAME` environment variable in any HTTP request.
832
850
  details: |-
833
851
  This setting can be used to override the server-provided value of `SCRIPT_NAME`, which is most commonly used
834
852
  for hosting Nautobot in a subdirectory (e.g. _example.com/nautobot/_).
@@ -1205,7 +1223,7 @@ properties:
1205
1223
  !!! note
1206
1224
  The Docker container normally attempts to run migrations on startup; however, if the database is
1207
1225
  in a read-only state the Docker container will fail to start. Setting the environment variable
1208
- [`NAUTOBOT_DOCKER_SKIP_INIT`](../installation-extras/docker.md#nautobot_docker_skip_init) to `true`
1226
+ [`NAUTOBOT_DOCKER_SKIP_INIT`](../guides/docker.md#nautobot_docker_skip_init) to `true`
1209
1227
  will prevent the migrations from occurring.
1210
1228
 
1211
1229
  !!! note
@@ -1609,7 +1627,6 @@ properties:
1609
1627
  In the case of a highly available installation with multiple web servers, `SECRET_KEY` must be identical
1610
1628
  among all servers in order to maintain a persistent user session state.
1611
1629
  environment_variable: "NAUTOBOT_SECRET_KEY"
1612
- is_required_setting: true
1613
1630
  type: "string"
1614
1631
  SESSION_CACHE_ALIAS:
1615
1632
  default: "default"
@@ -64,9 +64,16 @@ class NautobotTestRunner(DiscoverRunner):
64
64
  action="store_true",
65
65
  help="Save test database to a json fixture file to re-use on subsequent tests.",
66
66
  )
67
+ parser.add_argument(
68
+ "--no-reusedb",
69
+ action="store_false",
70
+ dest="reusedb",
71
+ help="Supplement to --keepdb; if --no-reusedb is set an existing database will NOT be reused.",
72
+ )
67
73
 
68
- def __init__(self, cache_test_fixtures=False, **kwargs):
74
+ def __init__(self, cache_test_fixtures=False, reusedb=True, **kwargs):
69
75
  self.cache_test_fixtures = cache_test_fixtures
76
+ self.reusedb = reusedb
70
77
 
71
78
  # Assert "integration" hasn't been provided w/ --tag
72
79
  incoming_tags = kwargs.get("tags") or []
@@ -118,7 +125,9 @@ class NautobotTestRunner(DiscoverRunner):
118
125
  connection.creation.create_test_db(
119
126
  verbosity=self.verbosity,
120
127
  autoclobber=not self.interactive,
121
- keepdb=self.keepdb,
128
+ keepdb=self.keepdb
129
+ # Extra check added for Nautobot:
130
+ and self.reusedb,
122
131
  serialize=connection.settings_dict["TEST"].get("SERIALIZE", True),
123
132
  )
124
133
 
@@ -149,7 +158,7 @@ class NautobotTestRunner(DiscoverRunner):
149
158
  verbosity=self.verbosity,
150
159
  keepdb=self.keepdb
151
160
  # Extra check added for Nautobot:
152
- and not settings.TEST_USE_FACTORIES,
161
+ and self.reusedb,
153
162
  )
154
163
 
155
164
  # Configure all other connections as mirrors of the first one
@@ -179,9 +188,7 @@ class NautobotTestRunner(DiscoverRunner):
179
188
  connection.creation.destroy_test_db(
180
189
  suffix=str(index + 1),
181
190
  verbosity=self.verbosity,
182
- keepdb=self.keepdb
183
- # Extra check added for Nautobot
184
- and not settings.TEST_USE_FACTORIES,
191
+ keepdb=self.keepdb,
185
192
  )
186
193
 
187
194
  # Extra block added for Nautobot
@@ -35,10 +35,13 @@ SETTINGS_DOCUMENTATION_SCHEMA = {
35
35
  "environment_variable": {
36
36
  "type": "string",
37
37
  },
38
- "is_constance_config": {
39
- "type": "boolean",
38
+ "environment_variables": {
39
+ "type": "array",
40
+ "items": {
41
+ "type": "string",
42
+ },
40
43
  },
41
- "is_required_setting": {
44
+ "is_constance_config": {
42
45
  "type": "boolean",
43
46
  },
44
47
  "see_also": {
@@ -122,6 +125,7 @@ class SettingsJSONSchemaTestCase(TestCase):
122
125
  "CONSTANCE_BACKEND",
123
126
  "CONSTANCE_CONFIG",
124
127
  "CONSTANCE_CONFIG_FIELDSETS",
128
+ "CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT",
125
129
  "CONSTANCE_DATABASE_CACHE_BACKEND",
126
130
  "CONSTANCE_DATABASE_PREFIX",
127
131
  "CSRF_FAILURE_VIEW",
@@ -1,8 +1,9 @@
1
1
  import re
2
- from unittest import mock
2
+ from unittest import mock, skipIf
3
3
  import urllib.parse
4
4
 
5
5
  from django.apps import apps
6
+ from django.conf import settings
6
7
  from django.contrib.contenttypes.models import ContentType
7
8
  from django.core.files.uploadedfile import SimpleUploadedFile
8
9
  from django.test import override_settings, RequestFactory
@@ -572,3 +573,41 @@ class SilkUIAccessTestCase(TestCase):
572
573
 
573
574
  # Check for success status code (e.g., 200)
574
575
  self.assertEqual(response.status_code, 200)
576
+
577
+
578
+ class ExampleViewWithCustomPermissionsTest(TestCase):
579
+ @skipIf(
580
+ "example_app" not in settings.PLUGINS,
581
+ "example_app not in settings.PLUGINS",
582
+ )
583
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
584
+ def test_permission_classes_attribute_is_enforced(self):
585
+ """
586
+ If example app is installed, check if the ViewWithCustomPermissions
587
+ is enforcing the permissions specified in its `permission_classes` attribute.
588
+ """
589
+ # Test IsAuthenticated permission
590
+ self.add_permissions("example_app.view_examplemodel")
591
+ self.client.logout()
592
+ url = reverse("plugins:example_app:view_with_custom_permissions")
593
+ response = self.client.get(url, follow=True)
594
+ self.assertHttpStatus(response, 200)
595
+ response_body = response.content.decode(response.charset)
596
+ # check if the user is redirected to the login page
597
+ self.assertIn(f'<input type="hidden" name="next" value="{url}" />', response_body)
598
+
599
+ # Test IsAdmin permission
600
+ self.client.force_login(self.user)
601
+ response = self.client.get(url, follow=True)
602
+ self.assertHttpStatus(response, 403)
603
+ response_body = response.content.decode(response.charset)
604
+ # check if the users have to have the permission to access the page
605
+ self.assertIn("You do not have permission to access this page.", response_body)
606
+
607
+ # View should be successfully accessed
608
+ self.user.is_staff = True
609
+ self.user.save()
610
+ response = self.client.get(url)
611
+ self.assertHttpStatus(response, 200)
612
+ response_body = response.content.decode(response.charset)
613
+ self.assertIn("You are viewing a table of example models", response_body)
@@ -17,7 +17,7 @@ from django.db.models import ManyToManyField, ProtectedError, Q
17
17
  from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
18
18
  from django.http import HttpResponse
19
19
  from django.shortcuts import get_object_or_404, redirect, render
20
- from django.urls import reverse
20
+ from django.urls import resolve, reverse
21
21
  from django.utils.encoding import iri_to_uri
22
22
  from django.utils.html import format_html
23
23
  from django.utils.http import url_has_allowed_host_and_scheme
@@ -205,17 +205,16 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
205
205
  filter_form = None
206
206
  hide_hierarchy_ui = False
207
207
  clear_view = request.GET.get("clear_view", False)
208
+ resolved_path = resolve(request.path)
209
+ list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
208
210
 
209
211
  # If the user clicks on the clear view button, we do not check for global or user defaults
210
212
  if not clear_view and not request.GET.get("saved_view"):
211
213
  # Check if there is a default for this view for this specific user
212
- app_label, model_name = model._meta.label.split(".")
213
- view_name = f"{app_label}:{model_name.lower()}_list"
214
-
215
214
  if not isinstance(user, AnonymousUser):
216
215
  try:
217
216
  user_default_saved_view_pk = UserSavedViewAssociation.objects.get(
218
- user=user, view_name=view_name
217
+ user=user, view_name=list_url
219
218
  ).saved_view.pk
220
219
  # Saved view should either belong to the user or be public
221
220
  SavedView.objects.get(
@@ -229,7 +228,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
229
228
 
230
229
  # Check if there is a global default for this view
231
230
  try:
232
- global_saved_view = SavedView.objects.get(view=view_name, is_global_default=True)
231
+ global_saved_view = SavedView.objects.get(view=list_url, is_global_default=True)
233
232
  return redirect(reverse("extras:savedview", kwargs={"pk": global_saved_view.pk}))
234
233
  except ObjectDoesNotExist:
235
234
  pass
@@ -302,7 +301,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
302
301
  table_config_form = None
303
302
  current_saved_view = None
304
303
  current_saved_view_pk = self.request.GET.get("saved_view", None)
305
- list_url = validated_viewname(model, "list")
306
304
  # We are not using .restrict(request.user, "view") here
307
305
  # User should be able to see any saved view that he has the list view access to.
308
306
  if user.has_perms(["extras.view_savedview"]):
@@ -315,15 +313,17 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
315
313
  SavedView.objects.filter(view=list_url, owner=user).order_by("name").only("pk", "name")
316
314
  )
317
315
  saved_views = shared_saved_views | user_owned_saved_views
316
+
317
+ if current_saved_view_pk:
318
+ try:
319
+ # We are not using .restrict(request.user, "view") here
320
+ # User should be able to see any saved view that he has the list view access to.
321
+ current_saved_view = SavedView.objects.get(view=list_url, pk=current_saved_view_pk)
322
+ except ObjectDoesNotExist:
323
+ messages.error(request, f"Saved view {current_saved_view_pk} not found")
324
+
325
+ # Construct the objects table
318
326
  if self.table:
319
- # Construct the objects table
320
- if current_saved_view_pk:
321
- try:
322
- # We are not using .restrict(request.user, "view") here
323
- # User should be able to see any saved view that he has the list view access to.
324
- current_saved_view = SavedView.objects.get(view=list_url, pk=current_saved_view_pk)
325
- except ObjectDoesNotExist:
326
- messages.error(request, f"Saved view {current_saved_view_pk} not found")
327
327
  if self.request.GET.getlist("sort") or (
328
328
  current_saved_view is not None and current_saved_view.config.get("sort_order")
329
329
  ):
@@ -234,6 +234,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
234
234
  serializer_class = None
235
235
  table_class = None
236
236
  notes_form_class = NoteForm
237
+ permission_classes = []
237
238
 
238
239
  def get_permissions_for_model(self, model, actions):
239
240
  """
@@ -267,7 +268,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
267
268
  """
268
269
  user = self.request.user
269
270
  permission_required = self.get_required_permission()
270
- # Check that the user has been granted the required permission(s) one by one.
271
+ # Check that the user has been granted the required Nautobot-specific object permission(s) one by one.
271
272
  # In case the permission has `message` or `code`` attribute, we want to include those information in the permission_denied error.
272
273
  for permission in permission_required:
273
274
  # If the user does not have the permission required, we raise DRF's `NotAuthenticated` or `PermissionDenied` exception
@@ -280,6 +281,16 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
280
281
  code=getattr(permission, "code", None),
281
282
  )
282
283
 
284
+ # Check for drf-specific permissions (IsAutheticated, etc) in permission_classes which is empty by default.
285
+ # self.get_permissions() iterates through permissions specified in the `permission_classes` attribute.
286
+ for permission in self.get_permissions():
287
+ # If the user does not have the permission required, we raise DRF's `NotAuthenticated` or `PermissionDenied` exception
288
+ # which will be handled by self.handle_no_permission() in the UI appropriately in the dispatch() method
289
+ if not permission.has_permission(request, self):
290
+ self.permission_denied(
291
+ request, message=getattr(permission, "message", None), code=getattr(permission, "code", None)
292
+ )
293
+
283
294
  def dispatch(self, request, *args, **kwargs):
284
295
  """
285
296
  Override the default dispatch() method to check permissions first.
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
5
5
  from django.core.exceptions import ObjectDoesNotExist
6
6
  from django.db.models import Q
7
7
  from django.template import engines, loader
8
+ from django.urls import resolve
8
9
  from django_tables2 import RequestConfig
9
10
  from rest_framework import renderers
10
11
 
@@ -295,7 +296,8 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
295
296
  # Construct valid actions for list view.
296
297
  valid_actions = self.validate_action_buttons(view, request)
297
298
  # Query SavedViews for dropdown button
298
- list_url = validated_viewname(model, "list")
299
+ resolved_path = resolve(request.path)
300
+ list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
299
301
  saved_views = None
300
302
  if model.is_saved_view_model:
301
303
  # We are not using .restrict(request.user, "view") here
@@ -213,7 +213,7 @@ def handle_protectederror(obj_list, request, e):
213
213
  protected_objects = list(e.protected_objects)
214
214
  protected_count = len(protected_objects) if len(protected_objects) <= 50 else "More than 50"
215
215
  err_message = format_html(
216
- "Unable to delete <strong>{}</strong>. {} dependent objects were found: ",
216
+ str(e.args[0]) if e.args else "Unable to delete <strong>{}</strong>. {} dependent objects were found: ",
217
217
  ", ".join(str(obj) for obj in obj_list),
218
218
  protected_count,
219
219
  )
@@ -389,6 +389,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer):
389
389
  expand_devices = serializers.BooleanField(required=False, default=True)
390
390
  include_images = serializers.BooleanField(required=False, default=True)
391
391
  display_fullname = serializers.BooleanField(required=False, default=True)
392
+ is_occupied = serializers.BooleanField(required=False, allow_null=True, default=None)
392
393
 
393
394
  def validate(self, attrs):
394
395
  attrs.setdefault("unit_width", get_settings_or_config("RACK_ELEVATION_DEFAULT_UNIT_WIDTH"))
@@ -231,6 +231,8 @@ class RackViewSet(NautobotModelViewSet):
231
231
  exclude=data["exclude"],
232
232
  expand_devices=data["expand_devices"],
233
233
  )
234
+ if data["is_occupied"] is not None:
235
+ elevation = [u for u in elevation if u["occupied"] == data["is_occupied"]]
234
236
 
235
237
  # Enable filtering rack units by ID
236
238
  q = data["q"]
nautobot/dcim/forms.py CHANGED
@@ -1900,7 +1900,7 @@ class DeviceForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm, LocalC
1900
1900
  widget=APISelect(
1901
1901
  api_url="/api/dcim/racks/{{rack}}/elevation/",
1902
1902
  attrs={
1903
- "disabled-indicator": "device",
1903
+ "disabled-indicator": "occupied",
1904
1904
  "data-query-param-face": '["$face"]',
1905
1905
  },
1906
1906
  ),
@@ -29,7 +29,7 @@ class PowerPanelTable(BaseTable):
29
29
  location = tables.Column(linkify=True)
30
30
  power_feed_count = LinkedCountColumn(
31
31
  viewname="dcim:powerfeed_list",
32
- url_params={"power_panel_id": "pk"},
32
+ url_params={"power_panel": "pk"},
33
33
  verbose_name="Feeds",
34
34
  )
35
35
  tags = TagColumn(url_name="dcim:powerpanel_list")
@@ -16,7 +16,7 @@
16
16
  </tr>
17
17
  <tr>
18
18
  <td>Device Types</td>
19
- <td><a href="{% url 'dcim:devicetype_list' %}?device_family={{ object.name }}">{{ object.device_type_count }}</a></td>
19
+ <td><a href="{% url 'dcim:devicetype_list' %}?device_family={{ object.name }}">{{ device_type_count }}</a></td>
20
20
  </tr>
21
21
  <tr>
22
22
  <td>Total Devices</td>
@@ -139,7 +139,7 @@ class Mixins:
139
139
  super().setUpTestData()
140
140
  cls.device_type = DeviceType.objects.first()
141
141
  cls.manufacturer = cls.device_type.manufacturer
142
- cls.location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
142
+ cls.location = Location.objects.filter(location_type__name="Campus").first()
143
143
  cls.device_role = Role.objects.get_for_model(Device).first()
144
144
  cls.device_status = Status.objects.get_for_model(Device).first()
145
145
  cls.device = Device.objects.create(
@@ -539,6 +539,8 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModel
539
539
  def setUpTestData(cls):
540
540
  cls.status = Status.objects.get_for_model(Location).first()
541
541
  location_type = LocationType.objects.create(name="Location Type 1")
542
+ location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
543
+
542
544
  cls.locations = (
543
545
  Location.objects.create(name="Location 1", location_type=location_type, status=cls.status),
544
546
  Location.objects.create(name="Location 2", location_type=location_type, status=cls.status),
@@ -548,8 +550,6 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModel
548
550
  RackGroup.objects.create(location=cls.locations[1], name="Parent Rack Group 2"),
549
551
  )
550
552
 
551
- location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
552
-
553
553
  RackGroup.objects.create(
554
554
  location=cls.locations[0],
555
555
  name="Rack Group 1",
@@ -643,7 +643,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
643
643
 
644
644
  @classmethod
645
645
  def setUpTestData(cls):
646
- locations = Location.objects.all()[:2]
646
+ locations = Location.objects.filter(devices__isnull=False)[:2]
647
+ for location in locations:
648
+ location.location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
647
649
 
648
650
  rack_groups = (
649
651
  RackGroup.objects.create(location=locations[0], name="Rack Group 1"),
@@ -675,6 +677,20 @@ class RackTest(APIViewTestCases.APIViewTestCase):
675
677
  status=statuses[0],
676
678
  )
677
679
 
680
+ populated_rack = Rack.objects.create(
681
+ location=locations[0],
682
+ rack_group=rack_groups[0],
683
+ role=rack_roles[0],
684
+ name="Populated Rack",
685
+ status=statuses[0],
686
+ )
687
+ # Place a device in Rack 4
688
+ device = Device.objects.filter(location=populated_rack.location, rack=None).first()
689
+ device.rack = populated_rack
690
+ device.face = "front"
691
+ device.position = 10
692
+ device.save()
693
+
678
694
  cls.create_data = [
679
695
  {
680
696
  "name": "Test Rack 4",
@@ -742,6 +758,44 @@ class RackTest(APIViewTestCases.APIViewTestCase):
742
758
  response = self.client.get(url, params, **self.header)
743
759
  self.assertHttpStatus(response, 200)
744
760
 
761
+ def test_filter_rack_elevation_is_occupied(self):
762
+ """
763
+ Test filtering the list of rack elevations by occupied status.
764
+ """
765
+ rack = Rack.objects.get(name="Populated Rack")
766
+ self.add_permissions("dcim.view_rack")
767
+ url = reverse("dcim-api:rack-elevation", kwargs={"pk": rack.pk})
768
+ # Get all units first
769
+ params = {"face": "front"}
770
+ response = self.client.get(url, params, **self.header)
771
+ all_units = response.data["results"]
772
+ # Assert the count is equal to the number of units in the rack
773
+ self.assertEqual(len(all_units), rack.u_height)
774
+
775
+ # Next get only unoccupied units
776
+ params = {"face": "front", "is_occupied": False}
777
+ response = self.client.get(url, params, **self.header)
778
+ unoccupied_units = response.data["results"]
779
+ # Assert the count is more than 0
780
+ self.assertGreater(len(unoccupied_units), 0)
781
+ # Assert the unoccupied count is less than the total number of units
782
+ self.assertLess(len(unoccupied_units), len(all_units))
783
+
784
+ # Next get only occupied units
785
+ params = {"face": "front", "is_occupied": True}
786
+ response = self.client.get(url, params, **self.header)
787
+ occupied_units = response.data["results"]
788
+ # Assert the count is more than 0
789
+ self.assertGreater(len(occupied_units), 0)
790
+ # Assert the occupied count is less than the total number of units
791
+ self.assertLess(len(occupied_units), len(all_units))
792
+
793
+ # Assert that the sum of unoccupied and occupied units is equal to the total number of units
794
+ self.assertEqual(len(unoccupied_units) + len(occupied_units), len(all_units))
795
+ # Assert that the lists are mutually exclusive
796
+ self.assertEqual(len([unit for unit in unoccupied_units if unit in occupied_units]), 0)
797
+ self.assertEqual(len([unit for unit in occupied_units if unit in unoccupied_units]), 0)
798
+
745
799
  def test_get_rack_elevation_svg(self):
746
800
  """
747
801
  GET a single rack elevation in SVG format.
@@ -3349,7 +3349,7 @@ class CableTestCase(FilterTestCases.FilterTestCase):
3349
3349
 
3350
3350
  def test_device(self):
3351
3351
  """Test that the device filter returns all cables for a device and its modules."""
3352
- interfaces = Interface.objects.filter(cable__isnull=True)[:3]
3352
+ interfaces = list(Interface.objects.filter(cable__isnull=True)[:3])
3353
3353
  manufacturer = Manufacturer.objects.first()
3354
3354
  device_type = DeviceType.objects.create(
3355
3355
  manufacturer=manufacturer, model="Test Device Filter for Cable Device Type"
@@ -65,7 +65,6 @@ from nautobot.dcim.models import (
65
65
  from nautobot.extras import context_managers
66
66
  from nautobot.extras.choices import CustomFieldTypeChoices
67
67
  from nautobot.extras.models import CustomField, Role, SecretsGroup, Status
68
- from nautobot.ipam.factory import VLANGroupFactory
69
68
  from nautobot.ipam.models import IPAddress, IPAddressToInterface, Namespace, Prefix, VLAN, VLANGroup
70
69
  from nautobot.tenancy.models import Tenant
71
70
  from nautobot.users.models import User
@@ -2351,7 +2350,6 @@ class InterfaceTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
2351
2350
  vid=100,
2352
2351
  location=location_2,
2353
2352
  status=vlan_status,
2354
- vlan_group=VLANGroupFactory.create(location=location_2),
2355
2353
  )
2356
2354
 
2357
2355
  cls.namespace = Namespace.objects.create(name="dcim_test_interface_ip_addresses")