nautobot 2.1.7__py3-none-any.whl → 2.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (338) hide show
  1. nautobot/apps/api.py +1 -2
  2. nautobot/apps/utils.py +4 -0
  3. nautobot/apps/views.py +2 -0
  4. nautobot/circuits/api/urls.py +1 -2
  5. nautobot/circuits/api/views.py +0 -12
  6. nautobot/circuits/tests/integration/test_relationships.py +0 -4
  7. nautobot/core/api/routers.py +25 -3
  8. nautobot/core/api/utils.py +4 -0
  9. nautobot/core/api/views.py +21 -15
  10. nautobot/core/celery/schedulers.py +13 -0
  11. nautobot/core/choices.py +0 -21
  12. nautobot/core/models/__init__.py +1 -1
  13. nautobot/core/models/tree_queries.py +29 -7
  14. nautobot/core/releases.py +1 -1
  15. nautobot/core/settings.py +9 -0
  16. nautobot/core/settings_funcs.py +0 -18
  17. nautobot/core/signals.py +5 -5
  18. nautobot/core/tasks.py +7 -3
  19. nautobot/core/templates/admin/base.html +23 -94
  20. nautobot/core/templates/generic/object_list.html +2 -0
  21. nautobot/core/templates/graphene/graphiql.html +18 -47
  22. nautobot/core/templates/inc/footer.html +5 -5
  23. nautobot/core/templates/inc/nav_menu.html +0 -7
  24. nautobot/core/templates/nautobot_config.py.j2 +6 -0
  25. nautobot/core/templates/rest_framework/api.html +12 -5
  26. nautobot/core/testing/mixins.py +13 -5
  27. nautobot/core/tests/integration/test_plugin_navbar.py +7 -21
  28. nautobot/core/tests/integration/test_view_authentication.py +67 -0
  29. nautobot/core/tests/runner.py +25 -2
  30. nautobot/core/tests/test_graphql.py +2 -14
  31. nautobot/core/tests/test_models.py +3 -3
  32. nautobot/core/tests/test_navigations.py +67 -10
  33. nautobot/core/tests/test_releases.py +9 -3
  34. nautobot/core/tests/test_views.py +23 -16
  35. nautobot/core/utils/lookup.py +124 -0
  36. nautobot/core/views/__init__.py +3 -7
  37. nautobot/core/views/generic.py +9 -0
  38. nautobot/dcim/api/urls.py +1 -2
  39. nautobot/dcim/api/views.py +1 -12
  40. nautobot/dcim/choices.py +56 -0
  41. nautobot/dcim/models/racks.py +1 -3
  42. nautobot/dcim/navigation.py +1 -1
  43. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +67 -43
  44. nautobot/dcim/tests/test_api.py +3 -0
  45. nautobot/dcim/tests/test_filters.py +0 -28
  46. nautobot/dcim/views.py +5 -2
  47. nautobot/extras/api/urls.py +1 -2
  48. nautobot/extras/api/views.py +0 -10
  49. nautobot/extras/choices.py +14 -0
  50. nautobot/extras/models/customfields.py +93 -34
  51. nautobot/extras/models/groups.py +1 -1
  52. nautobot/extras/models/relationships.py +32 -19
  53. nautobot/extras/navigation.py +3 -2
  54. nautobot/extras/plugins/__init__.py +8 -0
  55. nautobot/extras/plugins/views.py +6 -9
  56. nautobot/extras/querysets.py +1 -1
  57. nautobot/extras/signals.py +12 -6
  58. nautobot/extras/templates/extras/customfield.html +22 -14
  59. nautobot/extras/templatetags/job_buttons.py +7 -0
  60. nautobot/extras/templatetags/plugins.py +5 -1
  61. nautobot/extras/tests/test_customfields.py +323 -287
  62. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  63. nautobot/extras/tests/test_jobs.py +2 -2
  64. nautobot/extras/tests/test_plugins.py +41 -0
  65. nautobot/extras/tests/test_relationships.py +31 -14
  66. nautobot/extras/tests/test_views.py +124 -1
  67. nautobot/extras/utils.py +7 -3
  68. nautobot/extras/views.py +10 -10
  69. nautobot/ipam/api/urls.py +1 -2
  70. nautobot/ipam/api/views.py +6 -13
  71. nautobot/ipam/tables.py +0 -1
  72. nautobot/ipam/tests/test_graphql.py +2 -3
  73. nautobot/ipam/views.py +12 -10
  74. nautobot/project-static/css/base.css +1 -0
  75. nautobot/project-static/docs/404.html +30 -2
  76. nautobot/project-static/docs/apps/index.html +30 -2
  77. nautobot/project-static/docs/apps/nautobot-apps.html +30 -2
  78. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +30 -2
  79. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +30 -2
  80. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +410 -410
  81. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +30 -2
  82. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +386 -358
  83. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +30 -2
  84. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +30 -2
  85. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +30 -2
  86. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +30 -2
  87. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +45 -17
  88. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +30 -2
  89. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +30 -2
  90. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +30 -2
  91. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +759 -602
  92. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +30 -2
  93. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -2
  94. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +30 -2
  95. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +528 -467
  96. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +205 -109
  97. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +30 -2
  98. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +1265 -785
  99. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1827 -1746
  100. nautobot/project-static/docs/development/apps/api/configuration-view.html +30 -2
  101. nautobot/project-static/docs/development/apps/api/database-backend-config.html +30 -2
  102. nautobot/project-static/docs/development/apps/api/models/django-admin.html +30 -2
  103. nautobot/project-static/docs/development/apps/api/models/global-search.html +30 -2
  104. nautobot/project-static/docs/development/apps/api/models/graphql.html +30 -2
  105. nautobot/project-static/docs/development/apps/api/models/index.html +30 -2
  106. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +31 -3
  107. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +30 -2
  108. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +30 -2
  109. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +30 -2
  110. nautobot/project-static/docs/development/apps/api/platform-features/index.html +30 -2
  111. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +30 -2
  112. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +30 -2
  113. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +30 -2
  114. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +30 -2
  115. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +30 -2
  116. nautobot/project-static/docs/development/apps/api/prometheus.html +30 -2
  117. nautobot/project-static/docs/development/apps/api/setup.html +30 -2
  118. nautobot/project-static/docs/development/apps/api/testing.html +33 -5
  119. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +30 -2
  120. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +30 -2
  121. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +30 -2
  122. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +33 -5
  123. nautobot/project-static/docs/development/apps/api/ui-extensions/object-detail-views.html +13 -5559
  124. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +5594 -0
  125. nautobot/project-static/docs/development/apps/api/ui-extensions/tabs.html +3 -3
  126. nautobot/project-static/docs/development/apps/api/views/base-template.html +30 -2
  127. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +44 -11
  128. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +47 -14
  129. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +30 -2
  130. nautobot/project-static/docs/development/apps/api/views/index.html +30 -2
  131. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +30 -2
  132. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +30 -2
  133. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +30 -2
  134. nautobot/project-static/docs/development/apps/api/views/notes.html +30 -2
  135. nautobot/project-static/docs/development/apps/api/views/rest-api.html +30 -2
  136. nautobot/project-static/docs/development/apps/api/views/urls.html +30 -2
  137. nautobot/project-static/docs/development/apps/index.html +30 -2
  138. nautobot/project-static/docs/development/apps/migration/code-updates.html +30 -2
  139. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +30 -2
  140. nautobot/project-static/docs/development/apps/migration/from-v1.html +30 -2
  141. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +30 -2
  142. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +30 -2
  143. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +30 -2
  144. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +30 -2
  145. nautobot/project-static/docs/development/apps/porting-from-netbox.html +30 -2
  146. nautobot/project-static/docs/development/core/application-registry.html +30 -2
  147. nautobot/project-static/docs/development/core/best-practices.html +33 -5
  148. nautobot/project-static/docs/development/core/bootstrap-ui.html +30 -2
  149. nautobot/project-static/docs/development/core/caching.html +5481 -0
  150. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +30 -2
  151. nautobot/project-static/docs/development/core/extending-models.html +33 -5
  152. nautobot/project-static/docs/development/core/generic-views.html +30 -2
  153. nautobot/project-static/docs/development/core/getting-started.html +49 -12
  154. nautobot/project-static/docs/development/core/homepage.html +30 -2
  155. nautobot/project-static/docs/development/core/index.html +30 -2
  156. nautobot/project-static/docs/development/core/model-features.html +30 -2
  157. nautobot/project-static/docs/development/core/natural-keys.html +30 -2
  158. nautobot/project-static/docs/development/core/navigation-menu.html +30 -2
  159. nautobot/project-static/docs/development/core/release-checklist.html +30 -2
  160. nautobot/project-static/docs/development/core/role-internals.html +30 -2
  161. nautobot/project-static/docs/development/core/style-guide.html +30 -2
  162. nautobot/project-static/docs/development/core/templates.html +30 -2
  163. nautobot/project-static/docs/development/core/testing.html +30 -2
  164. nautobot/project-static/docs/development/core/user-preferences.html +30 -2
  165. nautobot/project-static/docs/development/index.html +30 -2
  166. nautobot/project-static/docs/development/jobs/index.html +30 -2
  167. nautobot/project-static/docs/development/jobs/migration/from-v1.html +30 -2
  168. nautobot/project-static/docs/index.html +30 -2
  169. nautobot/project-static/docs/objects.inv +0 -0
  170. nautobot/project-static/docs/release-notes/index.html +30 -2
  171. nautobot/project-static/docs/release-notes/version-1.0.html +30 -2
  172. nautobot/project-static/docs/release-notes/version-1.1.html +30 -2
  173. nautobot/project-static/docs/release-notes/version-1.2.html +30 -2
  174. nautobot/project-static/docs/release-notes/version-1.3.html +30 -2
  175. nautobot/project-static/docs/release-notes/version-1.4.html +31 -3
  176. nautobot/project-static/docs/release-notes/version-1.5.html +30 -2
  177. nautobot/project-static/docs/release-notes/version-1.6.html +573 -134
  178. nautobot/project-static/docs/release-notes/version-2.0.html +30 -2
  179. nautobot/project-static/docs/release-notes/version-2.1.html +539 -170
  180. nautobot/project-static/docs/search/search_index.json +1 -1
  181. nautobot/project-static/docs/sitemap.xml +250 -240
  182. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  183. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +30 -2
  184. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +30 -2
  185. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +30 -2
  186. nautobot/project-static/docs/user-guide/administration/configuration/index.html +30 -2
  187. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +49 -2
  188. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +30 -2
  189. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +30 -2
  190. nautobot/project-static/docs/user-guide/administration/guides/caching.html +30 -2
  191. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +30 -2
  192. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +30 -2
  193. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +30 -2
  194. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +30 -2
  195. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +30 -2
  196. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +30 -2
  197. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +30 -2
  198. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +30 -2
  199. nautobot/project-static/docs/user-guide/administration/installation/docker.html +37 -5
  200. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +30 -2
  201. nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +6019 -0
  202. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +30 -2
  203. nautobot/project-static/docs/user-guide/administration/installation/index.html +30 -2
  204. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +30 -2
  205. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +30 -2
  206. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +33 -5
  207. nautobot/project-static/docs/user-guide/administration/installation/services.html +30 -2
  208. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +30 -2
  209. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +30 -2
  210. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +30 -2
  211. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +30 -2
  212. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +30 -2
  213. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +30 -2
  214. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +30 -2
  215. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +30 -2
  216. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +30 -2
  217. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +30 -2
  218. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +30 -2
  219. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +30 -2
  220. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +30 -2
  221. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +30 -2
  222. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +30 -2
  223. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +30 -2
  224. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +30 -2
  225. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +30 -2
  226. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +30 -2
  227. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +30 -2
  228. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +30 -2
  229. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +30 -2
  230. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +30 -2
  231. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +30 -2
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +30 -2
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +30 -2
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +30 -2
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +30 -2
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +30 -2
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +30 -2
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +30 -2
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +30 -2
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +30 -2
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +30 -2
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +30 -2
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +30 -2
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +30 -2
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +30 -2
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +30 -2
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +30 -2
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +30 -2
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +30 -2
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +30 -2
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +30 -2
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +30 -2
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +30 -2
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +30 -2
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +30 -2
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +30 -2
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +30 -2
  258. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +30 -2
  259. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +30 -2
  260. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +30 -2
  261. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +30 -2
  262. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +30 -2
  263. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +30 -2
  264. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +30 -2
  265. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +30 -2
  266. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +30 -2
  267. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +30 -2
  268. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +30 -2
  269. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +30 -2
  270. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +30 -2
  271. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +30 -2
  272. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +30 -2
  273. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +30 -2
  274. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +30 -2
  275. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +30 -2
  276. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +30 -2
  277. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +33 -5
  278. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +30 -2
  279. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +30 -2
  280. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +30 -2
  281. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +30 -2
  282. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +30 -2
  283. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +30 -2
  284. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +30 -2
  285. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +30 -2
  286. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +30 -2
  287. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +30 -2
  288. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +30 -2
  289. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +30 -2
  290. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +30 -2
  291. nautobot/project-static/docs/user-guide/index.html +30 -2
  292. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +30 -2
  293. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +30 -2
  294. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +111 -15
  295. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +30 -2
  296. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +30 -2
  297. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +30 -2
  298. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +30 -2
  299. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +30 -2
  300. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +30 -2
  301. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +30 -2
  302. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +30 -2
  303. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +30 -2
  304. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +30 -2
  305. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +30 -2
  306. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +30 -2
  307. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +30 -2
  308. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +30 -2
  309. nautobot/project-static/docs/user-guide/platform-functionality/note.html +30 -2
  310. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +30 -2
  311. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +30 -2
  312. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +30 -2
  313. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +30 -2
  314. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +30 -2
  315. nautobot/project-static/docs/user-guide/platform-functionality/role.html +30 -2
  316. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +30 -2
  317. nautobot/project-static/docs/user-guide/platform-functionality/status.html +30 -2
  318. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +30 -2
  319. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +30 -2
  320. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +30 -2
  321. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +30 -2
  322. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +30 -2
  323. nautobot/tenancy/api/urls.py +1 -2
  324. nautobot/tenancy/api/views.py +0 -12
  325. nautobot/tenancy/navigation.py +1 -1
  326. nautobot/tenancy/tests/test_filters.py +0 -168
  327. nautobot/users/api/urls.py +1 -2
  328. nautobot/users/api/views.py +2 -65
  329. nautobot/users/views.py +8 -8
  330. nautobot/virtualization/api/urls.py +1 -2
  331. nautobot/virtualization/api/views.py +0 -12
  332. nautobot/virtualization/tests/test_filters.py +0 -28
  333. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/METADATA +2 -2
  334. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/RECORD +338 -334
  335. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/LICENSE.txt +0 -0
  336. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/NOTICE +0 -0
  337. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/WHEEL +0 -0
  338. {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/entry_points.txt +0 -0
@@ -1016,7 +1016,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
1016
1016
  self.assertEqual(mock_get_queryset.call_count, 2)
1017
1017
 
1018
1018
  # Clean-up after ourselves
1019
- cache.delete(f"{group.__class__.__name__}.{group.id}.cached_members")
1019
+ cache.delete(group.members_cache_key)
1020
1020
 
1021
1021
  @override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=0)
1022
1022
  def test_member_caching_disabled(self):
@@ -10,6 +10,7 @@ import uuid
10
10
  from constance.test import override_config
11
11
  from django.conf import settings
12
12
  from django.contrib.contenttypes.models import ContentType
13
+ from django.core.cache import cache
13
14
  from django.core.files.uploadedfile import SimpleUploadedFile
14
15
  from django.core.management import call_command
15
16
  from django.core.management.base import CommandError
@@ -34,7 +35,6 @@ from nautobot.extras.choices import (
34
35
  )
35
36
  from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
36
37
  from nautobot.extras.jobs import get_job
37
- from nautobot.extras.utils import change_logged_models_queryset
38
38
 
39
39
 
40
40
  class JobTest(TestCase):
@@ -928,7 +928,7 @@ class JobHookTransactionTest(TransactionTestCase): # TODO: BaseModelTestCase mi
928
928
  # We need to explicitly clear it here to have tests pass.
929
929
  # This is not a problem during normal operation of Nautobot because content-types don't normally get deleted
930
930
  # and recreated while Nautobot is running.
931
- change_logged_models_queryset.cache_clear()
931
+ cache.delete("nautobot.extras.utils.change_logged_models_queryset")
932
932
 
933
933
  module = "job_hook_receiver"
934
934
  name = "TestJobHookReceiverLog"
@@ -740,3 +740,44 @@ class TestPluginCoreViewOverrides(TestCase):
740
740
  ),
741
741
  response.content,
742
742
  )
743
+
744
+
745
+ @skipIf(
746
+ "example_plugin" not in settings.PLUGINS,
747
+ "example_plugin not in settings.PLUGINS",
748
+ )
749
+ class PluginTemplateExtensionsTest(TestCase):
750
+ """
751
+ Test that registered TemplateExtensions inject content as expected
752
+ """
753
+
754
+ def setUp(self):
755
+ super().setUp()
756
+ self.location = Location.objects.first()
757
+ self.user.is_superuser = True
758
+ self.user.save()
759
+
760
+ def test_list_view_buttons(self):
761
+ response = self.client.get(reverse("dcim:location_list"))
762
+ response_body = extract_page_body(response.content.decode(response.charset))
763
+ self.assertIn("LOCATION CONTENT - BUTTONS LIST", response_body, msg=response_body)
764
+
765
+ def test_detail_view_buttons(self):
766
+ response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
767
+ response_body = extract_page_body(response.content.decode(response.charset))
768
+ self.assertIn("LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
769
+
770
+ def test_detail_view_left_page(self):
771
+ response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
772
+ response_body = extract_page_body(response.content.decode(response.charset))
773
+ self.assertIn("LOCATION CONTENT - LEFT PAGE", response_body, msg=response_body)
774
+
775
+ def test_detail_view_right_page(self):
776
+ response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
777
+ response_body = extract_page_body(response.content.decode(response.charset))
778
+ self.assertIn("LOCATION CONTENT - RIGHT PAGE", response_body, msg=response_body)
779
+
780
+ def test_detail_view_full_width_page(self):
781
+ response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
782
+ response_body = extract_page_body(response.content.decode(response.charset))
783
+ self.assertIn("LOCATION CONTENT - FULL WIDTH PAGE", response_body, msg=response_body)
@@ -428,19 +428,31 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
428
428
  str(error.exception),
429
429
  )
430
430
 
431
- def test_get_for_model_lru_cache_invalidation(self):
432
- """Test that the lru cache is properly invalidated when Relationships are created or deleted."""
431
+ def test_get_for_model_caching_and_cache_invalidation(self):
432
+ """Test that the cache is used and is properly invalidated when Relationships are created or deleted."""
433
433
 
434
434
  manager = Relationship.objects
435
- manager_methods = (manager.get_for_model, manager.get_for_model_source, manager.get_for_model_destination)
435
+ manager_methods = [
436
+ (manager.get_for_model, 2),
437
+ (manager.get_for_model_source, 1),
438
+ (manager.get_for_model_destination, 1),
439
+ ]
436
440
 
437
- for manager_method in manager_methods:
441
+ for manager_method, expected_queries in manager_methods:
438
442
  with self.subTest(manager_method=manager_method.__name__):
439
- qs1 = manager_method(Location)
443
+ manager_method(Location)
440
444
 
441
445
  # Assert that the cache is used when calling method a second time
442
- qs1_cached = manager_method(Location)
443
- self.assertTrue(qs1_cached is qs1)
446
+ with self.assertNumQueries(0):
447
+ manager_method(Location)
448
+
449
+ # Assert that different models are cached separately
450
+ with self.assertNumQueries(expected_queries):
451
+ manager_method(Rack)
452
+ with self.assertNumQueries(0):
453
+ manager_method(Rack)
454
+ with self.assertNumQueries(0):
455
+ manager_method(Location)
444
456
 
445
457
  # Assert that the cache is invalidated on object save
446
458
  relationship = Relationship(
@@ -453,13 +465,18 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
453
465
  type=RelationshipTypeChoices.TYPE_MANY_TO_MANY,
454
466
  )
455
467
  relationship.save()
456
- qs2 = manager_method(Location)
457
- self.assertFalse(qs2 is qs1)
458
-
459
- # Assert that the cache is invalidated on object delete
460
- relationship.delete()
461
- qs3 = manager_method(Location)
462
- self.assertNotIn(qs3, (qs1, qs2))
468
+ try:
469
+ with self.assertNumQueries(expected_queries):
470
+ manager_method(Location)
471
+ with self.assertNumQueries(0):
472
+ manager_method(Location)
473
+ finally:
474
+ # Assert that the cache is invalidated on object delete
475
+ relationship.delete()
476
+ with self.assertNumQueries(expected_queries):
477
+ manager_method(Location)
478
+ with self.assertNumQueries(0):
479
+ manager_method(Location)
463
480
 
464
481
 
465
482
  class RelationshipAssociationTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
@@ -3,6 +3,7 @@ from unittest import mock
3
3
  import urllib.parse
4
4
  import uuid
5
5
 
6
+ from django.conf import settings
6
7
  from django.contrib.auth import get_user_model
7
8
  from django.contrib.contenttypes.models import ContentType
8
9
  from django.core.exceptions import ValidationError
@@ -20,6 +21,7 @@ from nautobot.dcim.tests import test_views
20
21
  from nautobot.extras.choices import (
21
22
  CustomFieldTypeChoices,
22
23
  JobExecutionType,
24
+ LogLevelChoices,
23
25
  ObjectChangeActionChoices,
24
26
  SecretsGroupAccessTypeChoices,
25
27
  SecretsGroupSecretTypeChoices,
@@ -39,6 +41,7 @@ from nautobot.extras.models import (
39
41
  GraphQLQuery,
40
42
  Job,
41
43
  JobButton,
44
+ JobLogEntry,
42
45
  JobResult,
43
46
  Note,
44
47
  ObjectChange,
@@ -616,6 +619,49 @@ class DynamicGroupTestCase(
616
619
  "dynamic_group_memberships-MAX_NUM_FORMS": "1000",
617
620
  }
618
621
 
622
+ def test_get_object_dynamic_groups_anonymous(self):
623
+ url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
624
+ self.client.logout()
625
+ response = self.client.get(url, follow=True)
626
+ self.assertHttpStatus(response, 200)
627
+ self.assertRedirects(response, f"/login/?next={url}")
628
+
629
+ def test_get_object_dynamic_groups_without_permission(self):
630
+ url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
631
+ response = self.client.get(url)
632
+ self.assertHttpStatus(response, [403, 404])
633
+
634
+ def test_get_object_dynamic_groups_with_permission(self):
635
+ url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
636
+ self.add_permissions("dcim.view_device", "extras.view_dynamicgroup")
637
+ response = self.client.get(url)
638
+ self.assertHttpStatus(response, 200)
639
+ response_body = response.content.decode(response.charset)
640
+ self.assertIn("DG 1", response_body, msg=response_body)
641
+ self.assertIn("DG 2", response_body, msg=response_body)
642
+ self.assertIn("DG 3", response_body, msg=response_body)
643
+
644
+ def test_get_object_dynamic_groups_with_constrained_permission(self):
645
+ self.add_permissions("extras.view_dynamicgroup")
646
+ obj_perm = ObjectPermission(
647
+ name="View a device",
648
+ constraints={"pk": Device.objects.first().pk},
649
+ actions=["view"],
650
+ )
651
+ obj_perm.save()
652
+ obj_perm.users.add(self.user)
653
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
654
+
655
+ url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
656
+ response = self.client.get(url)
657
+ self.assertHttpStatus(response, 200)
658
+ response_body = response.content.decode(response.charset)
659
+ self.assertIn("DG 1", response_body, msg=response_body)
660
+
661
+ url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.last().pk})
662
+ response = self.client.get(url)
663
+ self.assertHttpStatus(response, 404)
664
+
619
665
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
620
666
  def test_edit_saved_filter(self):
621
667
  """Test that editing a filter works using the edit view."""
@@ -785,6 +831,34 @@ class GitRepositoryTestCase(
785
831
  self.form_data = form_data
786
832
  super().test_edit_object_with_constrained_permission()
787
833
 
834
+ def test_post_sync_repo_anonymous(self):
835
+ self.client.logout()
836
+ url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
837
+ response = self.client.post(url, follow=True)
838
+ self.assertHttpStatus(response, 200)
839
+ self.assertRedirects(response, f"/login/?next={url}")
840
+
841
+ def test_post_sync_repo_without_permission(self):
842
+ url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
843
+ response = self.client.post(url)
844
+ self.assertHttpStatus(response, [403, 404])
845
+
846
+ # TODO: mock/stub out `enqueue_pull_git_repository_and_refresh_data` and test successful POST with permissions
847
+
848
+ def test_post_dryrun_repo_anonymous(self):
849
+ self.client.logout()
850
+ url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
851
+ response = self.client.post(url, follow=True)
852
+ self.assertHttpStatus(response, 200)
853
+ self.assertRedirects(response, f"/login/?next={url}")
854
+
855
+ def test_post_dryrun_repo_without_permission(self):
856
+ url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
857
+ response = self.client.post(url)
858
+ self.assertHttpStatus(response, [403, 404])
859
+
860
+ # TODO: mock/stub out `enqueue_git_repository_diff_origin_and_local` and test successful POST with permissions
861
+
788
862
 
789
863
  class NoteTestCase(
790
864
  ViewTestCases.CreateObjectViewTestCase,
@@ -1502,6 +1576,34 @@ class JobResultTestCase(
1502
1576
  def setUpTestData(cls):
1503
1577
  JobResult.objects.create(name="pass.TestPass")
1504
1578
  JobResult.objects.create(name="fail.TestFail")
1579
+ JobLogEntry.objects.create(
1580
+ log_level=LogLevelChoices.LOG_INFO,
1581
+ job_result=JobResult.objects.first(),
1582
+ grouping="run",
1583
+ message="This is a test",
1584
+ )
1585
+
1586
+ def test_get_joblogentrytable_anonymous(self):
1587
+ url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
1588
+ self.client.logout()
1589
+ response = self.client.get(url, follow=True)
1590
+ self.assertHttpStatus(response, 200)
1591
+ self.assertRedirects(response, f"/login/?next={url}")
1592
+
1593
+ def test_get_joblogentrytable_without_permission(self):
1594
+ url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
1595
+ response = self.client.get(url)
1596
+ self.assertHttpStatus(response, [403, 404])
1597
+
1598
+ def test_get_joblogentrytable_with_permission(self):
1599
+ url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
1600
+ self.add_permissions("extras.view_jobresult", "extras.view_joblogentry")
1601
+ response = self.client.get(url)
1602
+ self.assertHttpStatus(response, 200)
1603
+ response_body = response.content.decode(response.charset)
1604
+ self.assertIn("This is a test", response_body)
1605
+
1606
+ # TODO test with constrained permissions on both JobResult and JobLogEntry records
1505
1607
 
1506
1608
 
1507
1609
  class JobTestCase(
@@ -2019,10 +2121,11 @@ class JobButtonRenderingTestCase(TestCase):
2019
2121
 
2020
2122
  def setUp(self):
2021
2123
  super().setUp()
2124
+ self.job = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
2022
2125
  self.job_button_1 = JobButton(
2023
2126
  name="JobButton 1",
2024
2127
  text="JobButton {{ obj.name }}",
2025
- job=Job.objects.get(job_class_name="TestJobButtonReceiverSimple"),
2128
+ job=self.job,
2026
2129
  confirmation=False,
2027
2130
  )
2028
2131
  self.job_button_1.validated_save()
@@ -2047,6 +2150,26 @@ class JobButtonRenderingTestCase(TestCase):
2047
2150
  self.assertIn(f"JobButton {self.location_type.name}", content, content)
2048
2151
  self.assertIn("Click me!", content, content)
2049
2152
 
2153
+ def test_task_queue_hidden_input_is_present(self):
2154
+ """
2155
+ Ensure that the job button respects the job class' task_queues and the job class task_queues[0]/default is passed as a hidden form input.
2156
+ """
2157
+ self.job.task_queues_override = True
2158
+ self.job.task_queues = ["overriden_queue", "default", "priority"]
2159
+ self.job.save()
2160
+ response = self.client.get(self.location_type.get_absolute_url(), follow=True)
2161
+ self.assertEqual(response.status_code, 200)
2162
+ content = extract_page_body(response.content.decode(response.charset))
2163
+ self.assertIn(f'<input type="hidden" name="_task_queue" value="{self.job.task_queues[0]}">', content, content)
2164
+ self.job.task_queues_override = False
2165
+ self.job.save()
2166
+ response = self.client.get(self.location_type.get_absolute_url(), follow=True)
2167
+ self.assertEqual(response.status_code, 200)
2168
+ content = extract_page_body(response.content.decode(response.charset))
2169
+ self.assertIn(
2170
+ f'<input type="hidden" name="_task_queue" value="{settings.CELERY_TASK_DEFAULT_QUEUE}">', content, content
2171
+ )
2172
+
2050
2173
  def test_view_object_with_unsafe_text(self):
2051
2174
  """Ensure that JobButton text can't be used as a vector for XSS."""
2052
2175
  self.job_button_1.text = '<script>alert("Hello world!")</script>'
nautobot/extras/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import collections
2
- from functools import lru_cache
3
2
  import hashlib
4
3
  import hmac
5
4
  import logging
@@ -9,6 +8,7 @@ import sys
9
8
  from django.apps import apps
10
9
  from django.conf import settings
11
10
  from django.contrib.contenttypes.models import ContentType
11
+ from django.core.cache import cache
12
12
  from django.core.validators import ValidationError
13
13
  from django.db import transaction
14
14
  from django.db.models import Q
@@ -104,12 +104,16 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
104
104
  return [_class for _class in apps.get_models() if hasattr(_class, "to_objectchange")]
105
105
 
106
106
 
107
- @lru_cache(maxsize=None)
108
107
  def change_logged_models_queryset():
109
108
  """
110
109
  Cacheable function for cases where we need this queryset many times, such as when saving multiple objects.
111
110
  """
112
- return ChangeLoggedModelsQuery().as_queryset()
111
+ cache_key = "nautobot.extras.utils.change_logged_models_queryset"
112
+ queryset = cache.get(cache_key)
113
+ if queryset is None:
114
+ queryset = ChangeLoggedModelsQuery().as_queryset()
115
+ cache.set(cache_key, queryset)
116
+ return queryset
113
117
 
114
118
 
115
119
  @deconstructible
nautobot/extras/views.py CHANGED
@@ -713,7 +713,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
713
713
  filterset = filters.DynamicGroupFilterSet
714
714
 
715
715
 
716
- class ObjectDynamicGroupsView(View):
716
+ class ObjectDynamicGroupsView(generic.GenericView):
717
717
  """
718
718
  Present a list of dynamic groups associated to a particular object.
719
719
  base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
@@ -912,18 +912,18 @@ def check_and_call_git_repository_function(request, pk, func):
912
912
  messages.error(request, "Unable to run job: Celery worker process not running.")
913
913
  return redirect(request.get_full_path(), permanent=False)
914
914
  else:
915
- repository = get_object_or_404(GitRepository, pk=pk)
915
+ repository = get_object_or_404(GitRepository.objects.restrict(request.user, "change"), pk=pk)
916
916
  job_result = func(repository, request.user)
917
917
 
918
918
  return redirect(job_result.get_absolute_url())
919
919
 
920
920
 
921
- class GitRepositorySyncView(View):
921
+ class GitRepositorySyncView(generic.GenericView):
922
922
  def post(self, request, pk):
923
923
  return check_and_call_git_repository_function(request, pk, enqueue_pull_git_repository_and_refresh_data)
924
924
 
925
925
 
926
- class GitRepositoryDryRunView(View):
926
+ class GitRepositoryDryRunView(generic.GenericView):
927
927
  def post(self, request, pk):
928
928
  return check_and_call_git_repository_function(request, pk, enqueue_git_repository_diff_origin_and_local)
929
929
 
@@ -1559,7 +1559,7 @@ class JobResultView(generic.ObjectView):
1559
1559
  }
1560
1560
 
1561
1561
 
1562
- class JobLogEntryTableView(View):
1562
+ class JobLogEntryTableView(generic.GenericView):
1563
1563
  """
1564
1564
  Display a table of `JobLogEntry` objects for a given `JobResult` instance.
1565
1565
  """
@@ -1567,7 +1567,7 @@ class JobLogEntryTableView(View):
1567
1567
  queryset = JobResult.objects.all()
1568
1568
 
1569
1569
  def get(self, request, pk=None):
1570
- instance = self.queryset.get(pk=pk)
1570
+ instance = get_object_or_404(self.queryset.restrict(request.user, "view"), pk=pk)
1571
1571
  filter_q = request.GET.get("q")
1572
1572
  if filter_q:
1573
1573
  queryset = instance.job_log_entries.filter(
@@ -1646,7 +1646,7 @@ class ObjectChangeView(generic.ObjectView):
1646
1646
  }
1647
1647
 
1648
1648
 
1649
- class ObjectChangeLogView(View):
1649
+ class ObjectChangeLogView(generic.GenericView):
1650
1650
  """
1651
1651
  Present a history of changes made to a particular object.
1652
1652
  base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
@@ -1730,7 +1730,7 @@ class NoteDeleteView(generic.ObjectDeleteView):
1730
1730
  queryset = Note.objects.all()
1731
1731
 
1732
1732
 
1733
- class ObjectNotesView(View):
1733
+ class ObjectNotesView(generic.GenericView):
1734
1734
  """
1735
1735
  Present a list of notes associated to a particular object.
1736
1736
  base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
@@ -1751,7 +1751,7 @@ class ObjectNotesView(View):
1751
1751
  "assigned_object_id": obj.pk,
1752
1752
  }
1753
1753
  )
1754
- notes_table = tables.NoteTable(obj.notes)
1754
+ notes_table = tables.NoteTable(obj.notes.restrict(request.user, "view"))
1755
1755
 
1756
1756
  # Apply the request context
1757
1757
  paginate = {
@@ -1972,7 +1972,7 @@ class SecretView(generic.ObjectView):
1972
1972
  }
1973
1973
 
1974
1974
 
1975
- class SecretProviderParametersFormView(View):
1975
+ class SecretProviderParametersFormView(generic.GenericView):
1976
1976
  """
1977
1977
  Helper view to SecretView; retrieve the HTML form appropriate for entering parameters for a given SecretsProvider.
1978
1978
  """
nautobot/ipam/api/urls.py CHANGED
@@ -2,8 +2,7 @@ from nautobot.core.api.routers import OrderedDefaultRouter
2
2
 
3
3
  from . import views
4
4
 
5
- router = OrderedDefaultRouter()
6
- router.APIRootView = views.IPAMRootView
5
+ router = OrderedDefaultRouter(view_name="IPAM")
7
6
 
8
7
  # Namespaces
9
8
  router.register("namespaces", views.NamespaceViewSet)
@@ -5,7 +5,6 @@ from drf_spectacular.utils import extend_schema
5
5
  from rest_framework import status
6
6
  from rest_framework.decorators import action
7
7
  from rest_framework.response import Response
8
- from rest_framework.routers import APIRootView
9
8
 
10
9
  from nautobot.core.models.querysets import count_related
11
10
  from nautobot.core.utils.config import get_settings_or_config
@@ -26,16 +25,6 @@ from nautobot.ipam.models import (
26
25
 
27
26
  from . import serializers
28
27
 
29
-
30
- class IPAMRootView(APIRootView):
31
- """
32
- IPAM API root view
33
- """
34
-
35
- def get_view_name(self):
36
- return "IPAM"
37
-
38
-
39
28
  #
40
29
  # Namespace
41
30
  #
@@ -118,7 +107,9 @@ class PrefixViewSet(NautobotModelViewSet):
118
107
  """
119
108
  prefix = get_object_or_404(self.queryset, pk=pk)
120
109
  if request.method == "POST":
121
- with cache.lock("available-prefixes", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT):
110
+ with cache.lock(
111
+ "nautobot.ipam.api.views.available_prefixes", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT
112
+ ):
122
113
  available_prefixes = prefix.get_available_prefixes()
123
114
 
124
115
  # Validate Requested Prefixes' length
@@ -201,7 +192,9 @@ class PrefixViewSet(NautobotModelViewSet):
201
192
 
202
193
  # Create the next available IP within the prefix
203
194
  if request.method == "POST":
204
- with cache.lock("available-ips", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT):
195
+ with cache.lock(
196
+ "nautobot.ipam.api.views.available_ips", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT
197
+ ):
205
198
  # Normalize to a list of objects
206
199
  requested_ips = request.data if isinstance(request.data, list) else [request.data]
207
200
 
nautobot/ipam/tables.py CHANGED
@@ -179,7 +179,6 @@ VLAN_LINK = """
179
179
  {% url 'ipam:vlan_add' %}\
180
180
  ?vid={{ record.vid }}&vlan_group={{ vlan_group.pk }}\
181
181
  {% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
182
- {% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
183
182
  " class="btn btn-xs btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>\
184
183
  {% else %}
185
184
  {{ record.available }} VLAN{{ record.available|pluralize }} available
@@ -1,4 +1,3 @@
1
- from django.test import override_settings
2
1
  from django.urls import reverse
3
2
  from rest_framework import status
4
3
 
@@ -9,8 +8,8 @@ class TestPrefix(APITestCase):
9
8
  def setUp(self):
10
9
  super().setUp()
11
10
  self.api_url = reverse("graphql-api")
11
+ self.add_permissions("ipam.view_prefix")
12
12
 
13
- @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
14
13
  def test_prefix_ip_version(self):
15
14
  """Test ip_version is available for a Prefix via GraphQL."""
16
15
  get_prefixes_query = """
@@ -23,7 +22,7 @@ class TestPrefix(APITestCase):
23
22
  }
24
23
  """
25
24
  payload = {"query": get_prefixes_query}
26
- response = self.client.post(self.api_url, payload, format="json")
25
+ response = self.client.post(self.api_url, payload, format="json", **self.header)
27
26
  self.assertEqual(response.status_code, status.HTTP_200_OK)
28
27
  prefixes = response.data["data"]["prefixes"]
29
28
  self.assertIsInstance(prefixes, list)
nautobot/ipam/views.py CHANGED
@@ -775,7 +775,7 @@ class IPAddressEditView(generic.ObjectEditView):
775
775
  _, error_msg = retrieve_interface_or_vminterface_from_request(request)
776
776
  if error_msg:
777
777
  messages.warning(request, error_msg)
778
- return redirect(self.get_return_url(request), default_return_url="ipam:ipaddress_add")
778
+ return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
779
779
 
780
780
  return super().dispatch(request, *args, **kwargs)
781
781
 
@@ -858,17 +858,17 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
858
858
  """
859
859
 
860
860
  queryset = IPAddress.objects.all()
861
- default_return_url = "ipam:ipaddress_add"
862
861
 
863
862
  def dispatch(self, request, *args, **kwargs):
864
- # Redirect user if an interface has not been provided
865
- if "interface" not in request.GET and "vminterface" not in request.GET:
866
- return redirect(self.get_return_url(request))
863
+ if request.user.is_authenticated:
864
+ # Redirect user if an interface has not been provided
865
+ if "interface" not in request.GET and "vminterface" not in request.GET:
866
+ return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
867
867
 
868
- _, error_msg = retrieve_interface_or_vminterface_from_request(request)
869
- if error_msg:
870
- messages.warning(request, error_msg)
871
- return redirect(self.get_return_url(request))
868
+ _, error_msg = retrieve_interface_or_vminterface_from_request(request)
869
+ if error_msg:
870
+ messages.warning(request, error_msg)
871
+ return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
872
872
 
873
873
  return super().dispatch(request, *args, **kwargs)
874
874
 
@@ -973,7 +973,9 @@ class IPAddressMergeView(view_mixins.GetReturnURLMixin, view_mixins.ObjectPermis
973
973
  # Check if there are at least two IP addresses for us to merge
974
974
  # and if the skip button is pressed instead.
975
975
  if "_skip" not in request.POST and not operation_invalid:
976
- with cache.lock("ipaddress_merge", blocking_timeout=15, timeout=settings.REDIS_LOCK_TIMEOUT):
976
+ with cache.lock(
977
+ "nautobot.ipam.views.ipaddress_merge", blocking_timeout=15, timeout=settings.REDIS_LOCK_TIMEOUT
978
+ ):
977
979
  with transaction.atomic():
978
980
  namespace = Namespace.objects.get(pk=merged_attributes.get("namespace"))
979
981
  status = Status.objects.get(pk=merged_attributes.get("status"))
@@ -27,6 +27,7 @@ body {
27
27
  transition-duration: var(--navbar-transition-duration);
28
28
  transition-timing-function: ease-in-out;
29
29
  transition-property: margin-left, width;
30
+ min-height: calc(100vh - 20px);
30
31
  }
31
32
  #main-content > .form {
32
33
  margin-top: 20px;
@@ -537,6 +537,20 @@
537
537
 
538
538
 
539
539
 
540
+ <li class="md-nav__item">
541
+ <a href="/projects/core/en/stable/user-guide/administration/installation/health-checks.html" class="md-nav__link">
542
+ Health Checks
543
+ </a>
544
+ </li>
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
540
554
  <li class="md-nav__item">
541
555
  <a href="/projects/core/en/stable/user-guide/administration/installation/selinux-troubleshooting.html" class="md-nav__link">
542
556
  SELinux Troubleshooting
@@ -3871,8 +3885,8 @@
3871
3885
 
3872
3886
 
3873
3887
  <li class="md-nav__item">
3874
- <a href="/projects/core/en/stable/development/apps/api/ui-extensions/object-detail-views.html" class="md-nav__link">
3875
- Object Detail Views and Tabs
3888
+ <a href="/projects/core/en/stable/development/apps/api/ui-extensions/object-views.html" class="md-nav__link">
3889
+ Object Detail & List Views
3876
3890
  </a>
3877
3891
  </li>
3878
3892
 
@@ -4571,6 +4585,20 @@
4571
4585
 
4572
4586
 
4573
4587
 
4588
+ <li class="md-nav__item">
4589
+ <a href="/projects/core/en/stable/development/core/caching.html" class="md-nav__link">
4590
+ Caching
4591
+ </a>
4592
+ </li>
4593
+
4594
+
4595
+
4596
+
4597
+
4598
+
4599
+
4600
+
4601
+
4574
4602
  <li class="md-nav__item">
4575
4603
  <a href="/projects/core/en/stable/development/core/extending-models.html" class="md-nav__link">
4576
4604
  Extending Models