nautobot 2.1.8__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 (296) 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/core/api/routers.py +25 -3
  7. nautobot/core/api/utils.py +4 -0
  8. nautobot/core/api/views.py +21 -15
  9. nautobot/core/settings.py +1 -0
  10. nautobot/core/templates/admin/base.html +23 -94
  11. nautobot/core/templates/graphene/graphiql.html +18 -47
  12. nautobot/core/templates/inc/footer.html +5 -5
  13. nautobot/core/templates/inc/nav_menu.html +0 -7
  14. nautobot/core/templates/rest_framework/api.html +12 -5
  15. nautobot/core/tests/integration/test_view_authentication.py +67 -0
  16. nautobot/core/tests/test_graphql.py +2 -14
  17. nautobot/core/tests/test_views.py +22 -16
  18. nautobot/core/utils/lookup.py +124 -0
  19. nautobot/core/views/__init__.py +3 -7
  20. nautobot/core/views/generic.py +9 -0
  21. nautobot/dcim/api/urls.py +1 -2
  22. nautobot/dcim/api/views.py +1 -12
  23. nautobot/dcim/models/racks.py +1 -3
  24. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +67 -43
  25. nautobot/dcim/tests/test_api.py +3 -0
  26. nautobot/dcim/views.py +5 -2
  27. nautobot/extras/api/urls.py +1 -2
  28. nautobot/extras/api/views.py +0 -10
  29. nautobot/extras/plugins/views.py +6 -9
  30. nautobot/extras/tests/test_views.py +101 -0
  31. nautobot/extras/views.py +10 -10
  32. nautobot/ipam/api/urls.py +1 -2
  33. nautobot/ipam/api/views.py +0 -11
  34. nautobot/ipam/tables.py +0 -1
  35. nautobot/ipam/tests/test_graphql.py +2 -3
  36. nautobot/ipam/views.py +9 -9
  37. nautobot/project-static/css/base.css +1 -0
  38. nautobot/project-static/docs/404.html +14 -0
  39. nautobot/project-static/docs/apps/index.html +14 -0
  40. nautobot/project-static/docs/apps/nautobot-apps.html +14 -0
  41. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +14 -0
  42. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +14 -0
  43. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +394 -408
  44. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +14 -0
  45. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +14 -0
  46. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +14 -0
  47. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +14 -0
  48. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +14 -0
  49. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +14 -0
  50. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +14 -0
  51. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +14 -0
  52. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +14 -0
  53. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +14 -0
  54. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +14 -0
  55. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +14 -0
  56. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +14 -0
  57. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +14 -0
  58. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +14 -0
  59. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +14 -0
  60. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +14 -0
  61. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +649 -183
  62. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1811 -1744
  63. nautobot/project-static/docs/development/apps/api/configuration-view.html +14 -0
  64. nautobot/project-static/docs/development/apps/api/database-backend-config.html +14 -0
  65. nautobot/project-static/docs/development/apps/api/models/django-admin.html +14 -0
  66. nautobot/project-static/docs/development/apps/api/models/global-search.html +14 -0
  67. nautobot/project-static/docs/development/apps/api/models/graphql.html +14 -0
  68. nautobot/project-static/docs/development/apps/api/models/index.html +14 -0
  69. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +14 -0
  70. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +14 -0
  71. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +14 -0
  72. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +14 -0
  73. nautobot/project-static/docs/development/apps/api/platform-features/index.html +14 -0
  74. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +14 -0
  75. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +14 -0
  76. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +14 -0
  77. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +14 -0
  78. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +14 -0
  79. nautobot/project-static/docs/development/apps/api/prometheus.html +14 -0
  80. nautobot/project-static/docs/development/apps/api/setup.html +14 -0
  81. nautobot/project-static/docs/development/apps/api/testing.html +14 -0
  82. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +14 -0
  83. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +14 -0
  84. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +14 -0
  85. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +14 -0
  86. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +14 -0
  87. nautobot/project-static/docs/development/apps/api/views/base-template.html +14 -0
  88. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +28 -9
  89. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +31 -12
  90. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +14 -0
  91. nautobot/project-static/docs/development/apps/api/views/index.html +14 -0
  92. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +14 -0
  93. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +14 -0
  94. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +14 -0
  95. nautobot/project-static/docs/development/apps/api/views/notes.html +14 -0
  96. nautobot/project-static/docs/development/apps/api/views/rest-api.html +14 -0
  97. nautobot/project-static/docs/development/apps/api/views/urls.html +14 -0
  98. nautobot/project-static/docs/development/apps/index.html +14 -0
  99. nautobot/project-static/docs/development/apps/migration/code-updates.html +14 -0
  100. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +14 -0
  101. nautobot/project-static/docs/development/apps/migration/from-v1.html +14 -0
  102. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +14 -0
  103. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +14 -0
  104. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +14 -0
  105. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +14 -0
  106. nautobot/project-static/docs/development/apps/porting-from-netbox.html +14 -0
  107. nautobot/project-static/docs/development/core/application-registry.html +14 -0
  108. nautobot/project-static/docs/development/core/best-practices.html +14 -0
  109. nautobot/project-static/docs/development/core/bootstrap-ui.html +14 -0
  110. nautobot/project-static/docs/development/core/caching.html +14 -0
  111. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +14 -0
  112. nautobot/project-static/docs/development/core/extending-models.html +14 -0
  113. nautobot/project-static/docs/development/core/generic-views.html +14 -0
  114. nautobot/project-static/docs/development/core/getting-started.html +33 -10
  115. nautobot/project-static/docs/development/core/homepage.html +14 -0
  116. nautobot/project-static/docs/development/core/index.html +14 -0
  117. nautobot/project-static/docs/development/core/model-features.html +14 -0
  118. nautobot/project-static/docs/development/core/natural-keys.html +14 -0
  119. nautobot/project-static/docs/development/core/navigation-menu.html +14 -0
  120. nautobot/project-static/docs/development/core/release-checklist.html +14 -0
  121. nautobot/project-static/docs/development/core/role-internals.html +14 -0
  122. nautobot/project-static/docs/development/core/style-guide.html +14 -0
  123. nautobot/project-static/docs/development/core/templates.html +14 -0
  124. nautobot/project-static/docs/development/core/testing.html +14 -0
  125. nautobot/project-static/docs/development/core/user-preferences.html +14 -0
  126. nautobot/project-static/docs/development/index.html +14 -0
  127. nautobot/project-static/docs/development/jobs/index.html +14 -0
  128. nautobot/project-static/docs/development/jobs/migration/from-v1.html +14 -0
  129. nautobot/project-static/docs/index.html +14 -0
  130. nautobot/project-static/docs/objects.inv +0 -0
  131. nautobot/project-static/docs/release-notes/index.html +14 -0
  132. nautobot/project-static/docs/release-notes/version-1.0.html +14 -0
  133. nautobot/project-static/docs/release-notes/version-1.1.html +14 -0
  134. nautobot/project-static/docs/release-notes/version-1.2.html +14 -0
  135. nautobot/project-static/docs/release-notes/version-1.3.html +14 -0
  136. nautobot/project-static/docs/release-notes/version-1.4.html +14 -0
  137. nautobot/project-static/docs/release-notes/version-1.5.html +14 -0
  138. nautobot/project-static/docs/release-notes/version-1.6.html +14 -0
  139. nautobot/project-static/docs/release-notes/version-2.0.html +14 -0
  140. nautobot/project-static/docs/release-notes/version-2.1.html +365 -159
  141. nautobot/project-static/docs/search/search_index.json +1 -1
  142. nautobot/project-static/docs/sitemap.xml +245 -240
  143. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  144. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +14 -0
  145. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +14 -0
  146. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +14 -0
  147. nautobot/project-static/docs/user-guide/administration/configuration/index.html +14 -0
  148. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +14 -0
  149. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +14 -0
  150. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +14 -0
  151. nautobot/project-static/docs/user-guide/administration/guides/caching.html +14 -0
  152. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +14 -0
  153. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +14 -0
  154. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +14 -0
  155. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +14 -0
  156. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +14 -0
  157. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +14 -0
  158. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +14 -0
  159. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +14 -0
  160. nautobot/project-static/docs/user-guide/administration/installation/docker.html +21 -3
  161. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +14 -0
  162. nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +6019 -0
  163. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +14 -0
  164. nautobot/project-static/docs/user-guide/administration/installation/index.html +14 -0
  165. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +14 -0
  166. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +14 -0
  167. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +17 -3
  168. nautobot/project-static/docs/user-guide/administration/installation/services.html +14 -0
  169. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +14 -0
  170. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +14 -0
  171. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +14 -0
  172. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +14 -0
  173. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +14 -0
  174. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +14 -0
  175. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +14 -0
  176. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +14 -0
  177. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +14 -0
  178. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +14 -0
  179. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +14 -0
  180. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +14 -0
  181. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +14 -0
  182. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +14 -0
  183. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +14 -0
  184. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +14 -0
  185. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +14 -0
  186. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +14 -0
  187. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +14 -0
  188. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +14 -0
  189. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +14 -0
  190. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +14 -0
  191. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +14 -0
  192. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +14 -0
  193. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +14 -0
  194. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +14 -0
  195. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +14 -0
  196. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +14 -0
  197. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +14 -0
  198. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +14 -0
  199. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +14 -0
  200. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +14 -0
  201. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +14 -0
  202. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +14 -0
  203. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +14 -0
  204. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +14 -0
  205. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +14 -0
  206. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +14 -0
  207. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +14 -0
  208. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +14 -0
  209. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +14 -0
  210. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +14 -0
  211. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +14 -0
  212. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +14 -0
  213. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +14 -0
  214. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +14 -0
  215. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +14 -0
  216. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +14 -0
  217. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +14 -0
  218. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +14 -0
  219. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +14 -0
  220. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +14 -0
  221. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +14 -0
  222. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +14 -0
  223. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +14 -0
  224. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +14 -0
  225. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +14 -0
  226. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +14 -0
  227. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +14 -0
  228. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +14 -0
  229. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +14 -0
  230. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +14 -0
  231. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +14 -0
  232. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +14 -0
  233. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +14 -0
  234. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +14 -0
  235. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +14 -0
  236. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +14 -0
  237. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +14 -0
  238. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +14 -0
  239. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +14 -0
  240. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +14 -0
  241. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +14 -0
  242. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +14 -0
  243. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +14 -0
  244. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +14 -0
  245. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +14 -0
  246. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +14 -0
  247. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +14 -0
  248. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +14 -0
  249. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +14 -0
  250. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +14 -0
  251. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +14 -0
  252. nautobot/project-static/docs/user-guide/index.html +14 -0
  253. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +14 -0
  254. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +14 -0
  255. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +14 -0
  256. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +14 -0
  257. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +14 -0
  258. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +14 -0
  259. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +14 -0
  260. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +14 -0
  261. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +14 -0
  262. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +14 -0
  263. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +14 -0
  264. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +14 -0
  265. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +14 -0
  266. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +14 -0
  267. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +14 -0
  268. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +14 -0
  269. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +14 -0
  270. nautobot/project-static/docs/user-guide/platform-functionality/note.html +14 -0
  271. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +14 -0
  272. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +14 -0
  273. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +14 -0
  274. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +14 -0
  275. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +14 -0
  276. nautobot/project-static/docs/user-guide/platform-functionality/role.html +14 -0
  277. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +14 -0
  278. nautobot/project-static/docs/user-guide/platform-functionality/status.html +14 -0
  279. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +14 -0
  280. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +14 -0
  281. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +14 -0
  282. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +14 -0
  283. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +14 -0
  284. nautobot/tenancy/api/urls.py +1 -2
  285. nautobot/tenancy/api/views.py +0 -12
  286. nautobot/users/api/urls.py +1 -2
  287. nautobot/users/api/views.py +2 -65
  288. nautobot/users/views.py +8 -8
  289. nautobot/virtualization/api/urls.py +1 -2
  290. nautobot/virtualization/api/views.py +0 -12
  291. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/METADATA +2 -2
  292. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/RECORD +296 -294
  293. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/LICENSE.txt +0 -0
  294. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/NOTICE +0 -0
  295. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/WHEEL +0 -0
  296. {nautobot-2.1.8.dist-info → nautobot-2.1.9.dist-info}/entry_points.txt +0 -0
@@ -315,25 +315,31 @@ class LoginUI(TestCase):
315
315
  sso_login_search_result = self.make_request()
316
316
  self.assertIsNotNone(sso_login_search_result)
317
317
 
318
- @override_settings(BANNER_TOP="Hello, Banner Top", BANNER_BOTTOM="Hello, Banner Bottom")
319
- def test_routes_redirect_back_to_login_unauthenticated(self):
320
- """Assert that api docs and graphql redirects to login page if user is unauthenticated."""
318
+ def test_graphql_redirects_back_to_login_unauthenticated(self):
319
+ """Assert that graphql redirects to login page if user is unauthenticated."""
321
320
  self.client.logout()
322
321
  headers = {"HTTP_ACCEPT": "text/html"}
323
- urls = [reverse("api_docs"), reverse("graphql")]
322
+ url = reverse("graphql")
323
+ response = self.client.get(url, follow=True, **headers)
324
+ self.assertHttpStatus(response, 200)
325
+ self.assertRedirects(response, f"/login/?next={url}")
326
+ response_content = response.content.decode(response.charset).replace("\n", "")
327
+ for footer_text in self.footer_elements:
328
+ self.assertNotIn(footer_text, response_content)
329
+
330
+ def test_api_docs_403_unauthenticated(self):
331
+ """Assert that api docs return a 403 Forbidden if user is unauthenticated."""
332
+ self.client.logout()
333
+ urls = [
334
+ reverse("api_docs"),
335
+ reverse("api_redocs"),
336
+ reverse("schema"),
337
+ reverse("schema_json"),
338
+ reverse("schema_yaml"),
339
+ ]
324
340
  for url in urls:
325
- response = self.client.get(url, follow=True, **headers)
326
- self.assertHttpStatus(response, 200)
327
- redirect_chain = [(f"/login/?next={url}", 302)]
328
- self.assertEqual(response.redirect_chain, redirect_chain)
329
- response_content = response.content.decode(response.charset).replace("\n", "")
330
- # Assert Footer items(`self.footer_elements`), Banner and Banner Top is hidden
331
- for footer_text in self.footer_elements:
332
- self.assertNotIn(footer_text, response_content)
333
- # Only API Docs implements BANNERS
334
- if url == urls[0]:
335
- self.assertNotIn("Hello, Banner Top", response_content)
336
- self.assertNotIn("Hello, Banner Bottom", response_content)
341
+ response = self.client.get(url)
342
+ self.assertHttpStatus(response, 403)
337
343
 
338
344
 
339
345
  class MetricsViewTestCase(TestCase):
@@ -1,12 +1,14 @@
1
1
  """Utilities for looking up related classes and information."""
2
2
 
3
3
  import inspect
4
+ import re
4
5
 
5
6
  from django.apps import apps
6
7
  from django.conf import settings
7
8
  from django.contrib.auth.models import Group
8
9
  from django.contrib.contenttypes.models import ContentType
9
10
  from django.db.models import Model
11
+ from django.urls import get_resolver, URLPattern, URLResolver
10
12
  from django.utils.module_loading import import_string
11
13
 
12
14
 
@@ -218,3 +220,125 @@ def get_created_and_last_updated_usernames_for_model(instance):
218
220
  last_updated_by = last_updated_by_record.user_name
219
221
 
220
222
  return created_by, last_updated_by
223
+
224
+
225
+ def get_url_patterns(urlconf=None, patterns_list=None, base_path="/"):
226
+ """
227
+ Recursively yield a list of registered URL patterns.
228
+
229
+ Args:
230
+ urlconf (URLConf): Python module such as `nautobot.core.urls`.
231
+ Default if unspecified is the value of `settings.ROOT_URLCONF`, i.e. the `nautobot.core.urls` module.
232
+ patterns_list (list): Used in recursion. Generally can be omitted on initial call.
233
+ Default if unspecified is the `url_patterns` attribute of the given `urlconf` module.
234
+ base_path (str): String to prepend to all URL patterns yielded.
235
+ Default if unspecified is the string `"/"`.
236
+
237
+ Yields:
238
+ (str): Each URL pattern defined in the given urlconf and its descendants
239
+
240
+ Examples:
241
+ >>> generator = get_url_patterns()
242
+ >>> next(generator)
243
+ '/'
244
+ >>> next(generator)
245
+ '/search/'
246
+ >>> next(generator)
247
+ '/login/'
248
+ >>> next(generator)
249
+ '/logout/'
250
+ >>> next(generator)
251
+ '/circuits/circuits/<uuid:pk>/terminations/swap/'
252
+
253
+ >>> import example_plugin.urls as example_urls
254
+ >>> for url_pattern in get_url_patterns(example_urls, base_path="/plugins/example-plugin/"):
255
+ ... print(url_pattern)
256
+ ...
257
+ /plugins/example-plugin/
258
+ /plugins/example-plugin/config/
259
+ /plugins/example-plugin/models/<uuid:pk>/dynamic-groups/
260
+ /plugins/example-plugin/other-models/<uuid:pk>/dynamic-groups/
261
+ /plugins/example-plugin/docs/
262
+ /plugins/example-plugin/circuits/<uuid:pk>/example-plugin-tab/
263
+ /plugins/example-plugin/devices/<uuid:pk>/example-plugin-tab-1/
264
+ /plugins/example-plugin/devices/<uuid:pk>/example-plugin-tab-2/
265
+ /plugins/example-plugin/override-target/
266
+ /plugins/example-plugin/^models/$
267
+ /plugins/example-plugin/^models/add/$
268
+ /plugins/example-plugin/^models/import/$
269
+ /plugins/example-plugin/^models/edit/$
270
+ /plugins/example-plugin/^models/delete/$
271
+ /plugins/example-plugin/^models/all-names/$
272
+ /plugins/example-plugin/^models/(?P<pk>[^/.]+)/$
273
+ /plugins/example-plugin/^models/(?P<pk>[^/.]+)/delete/$
274
+ /plugins/example-plugin/^models/(?P<pk>[^/.]+)/edit/$
275
+ /plugins/example-plugin/^models/(?P<pk>[^/.]+)/changelog/$
276
+ /plugins/example-plugin/^models/(?P<pk>[^/.]+)/notes/$
277
+ /plugins/example-plugin/^other-models/$
278
+ /plugins/example-plugin/^other-models/add/$
279
+ /plugins/example-plugin/^other-models/edit/$
280
+ /plugins/example-plugin/^other-models/delete/$
281
+ /plugins/example-plugin/^other-models/(?P<pk>[^/.]+)/$
282
+ /plugins/example-plugin/^other-models/(?P<pk>[^/.]+)/delete/$
283
+ /plugins/example-plugin/^other-models/(?P<pk>[^/.]+)/edit/$
284
+ /plugins/example-plugin/^other-models/(?P<pk>[^/.]+)/changelog/$
285
+ /plugins/example-plugin/^other-models/(?P<pk>[^/.]+)/notes/$
286
+ """
287
+ if urlconf is None:
288
+ urlconf = settings.ROOT_URLCONF
289
+ if patterns_list is None:
290
+ patterns_list = get_resolver(urlconf).url_patterns
291
+
292
+ for item in patterns_list:
293
+ if isinstance(item, URLPattern):
294
+ yield base_path + str(item.pattern)
295
+ elif isinstance(item, URLResolver):
296
+ # Recurse!
297
+ yield from get_url_patterns(urlconf, item.url_patterns, base_path + str(item.pattern))
298
+
299
+
300
+ def get_url_for_url_pattern(url_pattern):
301
+ """
302
+ Given a URL pattern, construct a URL string that would match that pattern.
303
+
304
+ Examples:
305
+ >>> get_url_for_url_pattern("/plugins/example-plugin/^models/(?P<pk>[^/.]+)/$")
306
+ '/plugins/example-plugin/models/00000000-0000-0000-0000-000000000000/'
307
+ >>> get_url_for_url_pattern("/circuits/circuit-terminations/<uuid:termination_a_id>/connect/<str:termination_b_type>/")
308
+ '/circuits/circuit-terminations/00000000-0000-0000-0000-000000000000/connect/string/'
309
+ """
310
+ url = url_pattern
311
+ # Fixup tokens in path-style "classic" view URLs:
312
+ # "/admin/users/user/<id>/password/"
313
+ url = re.sub(r"<id>", "00000000-0000-0000-0000-000000000000", url)
314
+ # "/silk/request/<uuid:request_id>/profile/<int:profile_id>/"
315
+ url = re.sub(r"<int:\w+>", "1", url)
316
+ # "/admin/admin/logentry/<path:object_id>/"
317
+ url = re.sub(r"<path:\w+>", "1", url)
318
+ # "/dcim/sites/<slug:slug>/"
319
+ url = re.sub(r"<slug:\w+>", "slug", url)
320
+ # "/apps/installed-apps/<str:app>/"
321
+ url = re.sub(r"<str:\w+>", "string", url)
322
+ # "/dcim/locations/<uuid:pk>/"
323
+ url = re.sub(r"<uuid:\w+>", "00000000-0000-0000-0000-000000000000", url)
324
+ # tokens in regexp-style router urls, including REST and NautobotUIViewSet:
325
+ # "/extras/^external-integrations/(?P<pk>[^/.]+)/$"
326
+ # "/api/virtualization/^interfaces/(?P<pk>[^/.]+)/$"
327
+ # "/api/virtualization/^interfaces/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$"
328
+ url = re.sub(r"[$^]", "", url)
329
+ url = re.sub(r"/\?", "/", url)
330
+ url = re.sub(r"\(\?P<app_label>[^)]+\)", "users", url)
331
+ url = re.sub(r"\(\?P<class_path>[^)]+\)", "foo/bar/baz", url)
332
+ url = re.sub(r"\(\?P<format>[^)]+\)", "json", url)
333
+ url = re.sub(r"\(\?P<name>[^)]+\)", "string", url)
334
+ url = re.sub(r"\(\?P<pk>[^)]+\)", "00000000-0000-0000-0000-000000000000", url)
335
+ url = re.sub(r"\(\?P<slug>[^)]+\)", "string", url)
336
+ url = re.sub(r"\(\?P<url>[^)]+\)", "any", url)
337
+ # Fallthru for generic URL parameters
338
+ url = re.sub(r"\(\?P<\w+>[^)]+\)\??", "unknown", url)
339
+ url = re.sub(r"\\", "", url)
340
+
341
+ if any(char in url for char in "<>[]()?+^$"):
342
+ raise RuntimeError(f"Unhandled token in URL {url} derived from {url_pattern}")
343
+
344
+ return url
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import permission_required
11
11
  from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
12
12
  from django.contrib.contenttypes.models import ContentType
13
13
  from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
14
- from django.shortcuts import get_object_or_404, redirect, render
14
+ from django.shortcuts import get_object_or_404, render
15
15
  from django.template import loader, RequestContext, Template
16
16
  from django.template.exceptions import TemplateDoesNotExist
17
17
  from django.urls import resolve, reverse
@@ -210,7 +210,7 @@ class SearchView(AccessMixin, View):
210
210
  )
211
211
 
212
212
 
213
- class StaticMediaFailureView(View):
213
+ class StaticMediaFailureView(View): # NOT using LoginRequiredMixin here as this may happen even on the login page
214
214
  """
215
215
  Display a user-friendly error message with troubleshooting tips when a static media file fails to load.
216
216
  """
@@ -265,12 +265,8 @@ def csrf_failure(request, reason="", template_name="403_csrf_failure.html"):
265
265
  return HttpResponseForbidden(t.render(context), content_type="text/html")
266
266
 
267
267
 
268
- class CustomGraphQLView(GraphQLView):
268
+ class CustomGraphQLView(LoginRequiredMixin, GraphQLView):
269
269
  def render_graphiql(self, request, **data):
270
- if not request.user.is_authenticated:
271
- graphql_url = reverse("graphql")
272
- login_url = reverse(settings.LOGIN_URL)
273
- return redirect(f"{login_url}?next={graphql_url}")
274
270
  query_name = request.GET.get("name")
275
271
  if query_name:
276
272
  data["obj"] = GraphQLQuery.objects.get(name=query_name)
@@ -4,6 +4,7 @@ import re
4
4
 
5
5
  from django.conf import settings
6
6
  from django.contrib import messages
7
+ from django.contrib.auth.mixins import LoginRequiredMixin
7
8
  from django.contrib.contenttypes.models import ContentType
8
9
  from django.core.exceptions import (
9
10
  FieldDoesNotExist,
@@ -57,6 +58,14 @@ from nautobot.extras.models import ExportTemplate
57
58
  from nautobot.extras.utils import remove_prefix_from_cf_key
58
59
 
59
60
 
61
+ class GenericView(LoginRequiredMixin, View):
62
+ """
63
+ Base class for non-object-related views.
64
+
65
+ Enforces authentication, which Django's base View does not by default.
66
+ """
67
+
68
+
60
69
  class ObjectView(ObjectPermissionRequiredMixin, View):
61
70
  """
62
71
  Retrieve a single object for display.
nautobot/dcim/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.DCIMRootView
5
+ router = OrderedDefaultRouter(view_name="DCIM")
7
6
 
8
7
  # Locations
9
8
  router.register("location-types", views.LocationTypeViewSet)
@@ -13,7 +13,6 @@ from rest_framework.decorators import action
13
13
  from rest_framework.mixins import ListModelMixin
14
14
  from rest_framework.permissions import IsAuthenticated
15
15
  from rest_framework.response import Response
16
- from rest_framework.routers import APIRootView
17
16
  from rest_framework.viewsets import GenericViewSet, ViewSet
18
17
 
19
18
  from nautobot.circuits.models import Circuit
@@ -69,16 +68,6 @@ from nautobot.virtualization.models import VirtualMachine
69
68
  from . import serializers
70
69
  from .exceptions import MissingFilterException
71
70
 
72
-
73
- class DCIMRootView(APIRootView):
74
- """
75
- DCIM API root view
76
- """
77
-
78
- def get_view_name(self):
79
- return "DCIM"
80
-
81
-
82
71
  # Mixins
83
72
 
84
73
 
@@ -762,7 +751,7 @@ class ConnectedDeviceViewSet(ViewSet):
762
751
 
763
752
  # Determine local interface from peer interface's connection
764
753
  peer_interface = get_object_or_404(
765
- Interface.objects.all(),
754
+ Interface.objects.restrict(request.user, "view"),
766
755
  device__name=peer_device_name,
767
756
  name=peer_interface_name,
768
757
  )
@@ -1,5 +1,3 @@
1
- from collections import OrderedDict
2
-
3
1
  from django.conf import settings
4
2
  from django.contrib.contenttypes.fields import GenericRelation
5
3
  from django.contrib.contenttypes.models import ContentType
@@ -274,7 +272,7 @@ class Rack(PrimaryModel):
274
272
  contains a height attribute for the device
275
273
  """
276
274
 
277
- elevation = OrderedDict()
275
+ elevation = {}
278
276
  for u in self.units:
279
277
  elevation[u] = {
280
278
  "id": u,
@@ -15,6 +15,7 @@
15
15
  <th>Interface</th>
16
16
  <th>Configured Device</th>
17
17
  <th>Configured Interface</th>
18
+ <th>Configured MAC Address</th>
18
19
  <th>LLDP Device</th>
19
20
  <th>LLDP Interface</th>
20
21
  </tr>
@@ -27,18 +28,21 @@
27
28
  <td class="configured_device" data="{{ iface.connected_endpoint.device }}" data-chassis="{{ iface.connected_endpoint.device.virtual_chassis.name }}">
28
29
  {{ iface.connected_endpoint.device|hyperlinked_object }}
29
30
  </td>
30
- <td class="configured_interface" data="{{ iface.connected_endpoint }}">
31
+ <td class="configured_interface" data-interface-name="{{ iface.connected_endpoint }}">
31
32
  <span title="{{ iface.connected_endpoint.get_type_display }}">{{ iface.connected_endpoint }}</span>
32
33
  </td>
34
+ <td class="configured_mac" data-mac-address="{{ iface.connected_endpoint.mac_address }}">
35
+ <span>{{ iface.connected_endpoint.mac_address }}</span>
36
+ </td>
33
37
  {% elif iface.connected_endpoint.circuit %}
34
38
  {% with circuit=iface.connected_endpoint.circuit %}
35
- <td colspan="2">
39
+ <td colspan="3">
36
40
  <i class="mdi mdi-lightning-bolt" title="Circuit"></i>
37
41
  <a href="{{ circuit.get_absolute_url }}">{{ circuit.provider }} {{ circuit }}</a>
38
42
  </td>
39
43
  {% endwith %}
40
44
  {% else %}
41
- <td colspan="2">None</td>
45
+ <td colspan="3">None</td>
42
46
  {% endif %}
43
47
  <td class="device"></td>
44
48
  <td class="interface"></td>
@@ -52,51 +56,71 @@
52
56
  {% block javascript %}
53
57
  {{ block.super }}
54
58
  <script type="text/javascript">
55
- $(document).ready(function() {
56
- $.ajax({
57
- url: "{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_lldp_neighbors_detail",
58
- dataType: 'json',
59
- success: function(json) {
60
- $.each(json['get_lldp_neighbors_detail'], function(iface, neighbors) {
61
- var neighbor = neighbors[0];
62
- var row = $('*[data-interface-name="' + iface.split(".")[0].replace(/([\/:])/g, "\\$1") + '"]');
59
+ var ready = (callback) => {
60
+ if (document.readyState != "loading") {
61
+ callback();
62
+ } else {
63
+ document.addEventListener("DOMContentLoaded", callback);
64
+ }
65
+ };
66
+
67
+ ready(() => {
68
+ fetch("{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_lldp_neighbors_detail")
69
+ .then((response) => {
70
+ if (!response.ok) {
71
+ throw Error(response.statusText);
72
+ }
73
+ return response.json();
74
+ })
75
+ .then((data) => {
76
+ const interfaces = data["get_lldp_neighbors_detail"];
77
+ for (var iface of Object.keys(interfaces)) {
78
+ const neighbor = interfaces[iface][0];
79
+ const row = document.querySelector('*[data-interface-name="'+ iface.split(".")[0].replace(/([\/:])/g, "\\$1") + '"]');
80
+ // var row = $('*[data-interface-name="' + iface.split(".")[0].replace(/([\/:])/g, "\\$1") + '"]');
63
81
 
64
- // Glean configured hostnames/interfaces from the DOM
65
- var configured_device = row.children('td.configured_device').attr('data');
66
- var configured_chassis = row.children('td.configured_device').attr('data-chassis');
67
- var configured_interface = row.children('td.configured_interface').attr('data');
68
- var configured_interface_short = null;
69
- if (configured_interface) {
70
- // Match long-form IOS names against short ones (e.g. Gi0/1 == GigabitEthernet0/1).
71
- configured_interface_short = configured_interface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, "$1$2");
72
- }
82
+ // Glean configured hostnames/interfaces from the DOM
83
+ const configured_device = row.querySelector('td.configured_device').getAttribute('data');
84
+ const configured_chassis = row.querySelector('td.configured_device').getAttribute('data-chassis');
85
+ const configured_interface = row.querySelector('td.configured_interface').getAttribute('data-interface-name').toLowerCase();
86
+ const configured_mac_address = row.querySelector('td.configured_mac').getAttribute('data-mac-address').toLowerCase();
87
+ let configured_interface_short = null;
88
+ if (configured_interface) {
89
+ // Match long-form IOS names against short ones (e.g. Gi0/1 == GigabitEthernet0/1).
90
+ configured_interface_short = configured_interface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, "$1$2");
91
+ }
73
92
 
74
- // Clean up hostnames/interfaces learned via LLDP
75
- var neighbor_host = neighbor['remote_system_name'] || ""; // sanitize hostname if it's null to avoid breaking the split func
76
- var neighbor_port = neighbor['remote_port'] || ""; // sanitize port if it's null to avoid breaking the split func
77
- var lldp_device = neighbor_host.split(".")[0]; // Strip off any trailing domain name
78
- var lldp_interface = neighbor_port.split(".")[0]; // Strip off any trailing subinterface ID
93
+ // Clean up hostnames/interfaces learned via LLDP
94
+ const neighbor_host = neighbor['remote_system_name'] || ""; // sanitize hostname if it's null to avoid breaking the split func
95
+ const neighbor_port = neighbor['remote_port'] || ""; // sanitize port if it's null to avoid breaking the split func
96
+ const lldp_device = neighbor_host.split(".")[0]; // Strip off any trailing domain name
97
+ const lldp_interface = neighbor_port.split(".")[0].toLowerCase(); // Strip off any trailing subinterface ID
79
98
 
80
- // Add LLDP neighbors to table
81
- row.children('td.device').html(lldp_device);
82
- row.children('td.interface').html(lldp_interface);
99
+ // Add LLDP neighbors to table
100
+ row.querySelector('td.device').textContent = lldp_device;
101
+ row.querySelector('td.interface').textContent = lldp_interface;
83
102
 
84
- // Apply colors to rows
85
- if (!configured_device && lldp_device) {
86
- row.addClass('info');
87
- } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface == lldp_interface) {
88
- row.addClass('success');
89
- } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface_short == lldp_interface) {
90
- row.addClass('success');
91
- } else {
92
- row.addClass('danger');
93
- }
94
- });
95
- },
96
- error: function(xhr) {
97
- alert(xhr.responseText);
103
+ // Apply colors to rows
104
+ if (!configured_device && lldp_device) {
105
+ row.classList.add('info');
106
+ } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface == lldp_interface) {
107
+ row.classList.add('success');
108
+ } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface_short == lldp_interface) {
109
+ row.classList.add('success');
110
+ } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_mac_address == lldp_interface) {
111
+ row.classList.add('success');
112
+ } else {
113
+ row.classList.add('danger');
114
+ }
115
+ }
116
+ })
117
+ .catch((error) => {
118
+ if (error.responseText) {
119
+ alert(error.responseText);
120
+ } else {
121
+ throw error;
98
122
  }
99
123
  });
100
124
  });
101
125
  </script>
102
- {% endblock %}
126
+ {% endblock %}
@@ -2092,7 +2092,10 @@ class ConnectedDeviceTest(APITestCase):
2092
2092
  def test_get_connected_device(self):
2093
2093
  url = reverse("dcim-api:connected-device-list")
2094
2094
  response = self.client.get(url + "?peer_device=TestDevice2&peer_interface=eth0", **self.header)
2095
+ self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
2095
2096
 
2097
+ self.add_permissions("dcim.view_interface")
2098
+ response = self.client.get(url + "?peer_device=TestDevice2&peer_interface=eth0", **self.header)
2096
2099
  self.assertHttpStatus(response, status.HTTP_200_OK)
2097
2100
  self.assertEqual(response.data["name"], self.device1.name)
2098
2101
 
nautobot/dcim/views.py CHANGED
@@ -11,7 +11,7 @@ from django.forms import (
11
11
  ModelMultipleChoiceField,
12
12
  MultipleHiddenInput,
13
13
  )
14
- from django.shortcuts import get_object_or_404, redirect, render
14
+ from django.shortcuts import get_object_or_404, HttpResponse, redirect, render
15
15
  from django.utils.functional import cached_property
16
16
  from django.utils.html import format_html
17
17
  from django.views.generic import View
@@ -2316,7 +2316,7 @@ class CableCreateView(generic.ObjectEditView):
2316
2316
  "rear-port": forms.ConnectCableToRearPortForm,
2317
2317
  "power-feed": forms.ConnectCableToPowerFeedForm,
2318
2318
  "circuit-termination": forms.ConnectCableToCircuitTerminationForm,
2319
- }[kwargs.get("termination_b_type")]
2319
+ }.get(kwargs.get("termination_b_type"), None)
2320
2320
 
2321
2321
  return super().dispatch(request, *args, **kwargs)
2322
2322
 
@@ -2333,6 +2333,9 @@ class CableCreateView(generic.ObjectEditView):
2333
2333
  return obj
2334
2334
 
2335
2335
  def get(self, request, *args, **kwargs):
2336
+ if self.model_form is None:
2337
+ return HttpResponse(status_code=400)
2338
+
2336
2339
  obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
2337
2340
 
2338
2341
  # Parse initial data manually to avoid setting field values as lists
@@ -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.ExtrasRootView
5
+ router = OrderedDefaultRouter(view_name="Extras")
7
6
 
8
7
  # Computed Fields
9
8
  router.register("computed-fields", views.ComputedFieldViewSet)
@@ -16,7 +16,6 @@ from rest_framework.exceptions import MethodNotAllowed, PermissionDenied, Valida
16
16
  from rest_framework.parsers import JSONParser, MultiPartParser
17
17
  from rest_framework.permissions import IsAuthenticated
18
18
  from rest_framework.response import Response
19
- from rest_framework.routers import APIRootView
20
19
 
21
20
  from nautobot.core.api.authentication import TokenPermissions
22
21
  from nautobot.core.api.utils import get_serializer_for_model
@@ -74,15 +73,6 @@ from nautobot.extras.utils import get_worker_count
74
73
  from . import serializers
75
74
 
76
75
 
77
- class ExtrasRootView(APIRootView):
78
- """
79
- Extras API root view
80
- """
81
-
82
- def get_view_name(self):
83
- return "Extras"
84
-
85
-
86
76
  class NotesViewSetMixin:
87
77
  def restrict_queryset(self, request, *args, **kwargs):
88
78
  """
@@ -2,7 +2,6 @@ from collections import OrderedDict
2
2
 
3
3
  from django.apps import apps
4
4
  from django.conf import settings
5
- from django.contrib.auth.mixins import LoginRequiredMixin
6
5
  from django.http import Http404
7
6
  from django.shortcuts import render
8
7
  from django.urls.exceptions import NoReverseMatch
@@ -14,8 +13,9 @@ from rest_framework.response import Response
14
13
  from rest_framework.reverse import reverse
15
14
  from rest_framework.views import APIView
16
15
 
17
- from nautobot.core.api.views import NautobotAPIVersionMixin
16
+ from nautobot.core.api.views import AuthenticatedAPIRootView, NautobotAPIVersionMixin
18
17
  from nautobot.core.forms import TableConfigForm
18
+ from nautobot.core.views.generic import GenericView
19
19
  from nautobot.core.views.mixins import AdminRequiredMixin
20
20
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
21
21
  from nautobot.extras.plugins.tables import InstalledPluginsTable
@@ -67,7 +67,7 @@ class InstalledPluginsView(AdminRequiredMixin, View):
67
67
  )
68
68
 
69
69
 
70
- class InstalledPluginDetailView(LoginRequiredMixin, View):
70
+ class InstalledPluginDetailView(GenericView):
71
71
  """
72
72
  View for showing details of an installed plugin.
73
73
  """
@@ -92,7 +92,6 @@ class InstalledPluginsAPIView(NautobotAPIVersionMixin, APIView):
92
92
  """
93
93
 
94
94
  permission_classes = [permissions.IsAdminUser]
95
- _ignore_model_permissions = True
96
95
 
97
96
  def get_view_name(self):
98
97
  return "Installed Plugins"
@@ -128,11 +127,9 @@ class InstalledPluginsAPIView(NautobotAPIVersionMixin, APIView):
128
127
  return Response([self._get_plugin_data(apps.get_app_config(plugin)) for plugin in settings.PLUGINS])
129
128
 
130
129
 
131
- class PluginsAPIRootView(NautobotAPIVersionMixin, APIView):
132
- _ignore_model_permissions = True
133
-
134
- def get_view_name(self):
135
- return "Plugins"
130
+ class PluginsAPIRootView(AuthenticatedAPIRootView):
131
+ name = "Apps"
132
+ description = "API extension point for installed Nautobot Apps"
136
133
 
137
134
  @staticmethod
138
135
  def _get_plugin_entry(plugin, app_config, request, format_):