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
@@ -214,6 +214,7 @@ class FilterFormsTestCase(TestCase):
214
214
  )
215
215
  url = reverse("dcim:location_list") + query_param
216
216
  response = self.client.get(url)
217
+ self.assertHttpStatus(response, 200)
217
218
  response_content = response.content.decode(response.charset).replace("\n", "")
218
219
  self.assertInHTML(locations[0].name, response_content)
219
220
  self.assertInHTML(locations[1].name, response_content)
@@ -314,25 +315,31 @@ class LoginUI(TestCase):
314
315
  sso_login_search_result = self.make_request()
315
316
  self.assertIsNotNone(sso_login_search_result)
316
317
 
317
- @override_settings(BANNER_TOP="Hello, Banner Top", BANNER_BOTTOM="Hello, Banner Bottom")
318
- def test_routes_redirect_back_to_login_unauthenticated(self):
319
- """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."""
320
320
  self.client.logout()
321
321
  headers = {"HTTP_ACCEPT": "text/html"}
322
- 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
+ ]
323
340
  for url in urls:
324
- response = self.client.get(url, follow=True, **headers)
325
- self.assertHttpStatus(response, 200)
326
- redirect_chain = [(f"/login/?next={url}", 302)]
327
- self.assertEqual(response.redirect_chain, redirect_chain)
328
- response_content = response.content.decode(response.charset).replace("\n", "")
329
- # Assert Footer items(`self.footer_elements`), Banner and Banner Top is hidden
330
- for footer_text in self.footer_elements:
331
- self.assertNotIn(footer_text, response_content)
332
- # Only API Docs implements BANNERS
333
- if url == urls[0]:
334
- self.assertNotIn("Hello, Banner Top", response_content)
335
- self.assertNotIn("Hello, Banner Bottom", response_content)
341
+ response = self.client.get(url)
342
+ self.assertHttpStatus(response, 403)
336
343
 
337
344
 
338
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
  )
nautobot/dcim/choices.py CHANGED
@@ -222,6 +222,10 @@ class PowerPortTypeChoices(ChoiceSet):
222
222
  TYPE_IEC_3PNE4H = "iec-60309-3p-n-e-4h"
223
223
  TYPE_IEC_3PNE6H = "iec-60309-3p-n-e-6h"
224
224
  TYPE_IEC_3PNE9H = "iec-60309-3p-n-e-9h"
225
+ # IEC 60906-1
226
+ TYPE_IEC_60906_1 = "iec-60906-1"
227
+ TYPE_NBR_14136_10A = "nbr-14136-10a"
228
+ TYPE_NBR_14136_20A = "nbr-14136-20a"
225
229
  # NEMA non-locking
226
230
  TYPE_NEMA_115P = "nema-1-15p"
227
231
  TYPE_NEMA_515P = "nema-5-15p"
@@ -339,6 +343,14 @@ class PowerPortTypeChoices(ChoiceSet):
339
343
  (TYPE_IEC_3PNE9H, "3P+N+E 9H"),
340
344
  ),
341
345
  ),
346
+ (
347
+ "IEC 60906-1",
348
+ (
349
+ (TYPE_IEC_60906_1, "IEC 60906-1"),
350
+ (TYPE_NBR_14136_10A, "2P+T 10A (NBR 14136)"),
351
+ (TYPE_NBR_14136_20A, "2P+T 20A (NBR 14136)"),
352
+ ),
353
+ ),
342
354
  (
343
355
  "NEMA (Non-locking)",
344
356
  (
@@ -482,6 +494,10 @@ class PowerOutletTypeChoices(ChoiceSet):
482
494
  TYPE_IEC_3PNE4H = "iec-60309-3p-n-e-4h"
483
495
  TYPE_IEC_3PNE6H = "iec-60309-3p-n-e-6h"
484
496
  TYPE_IEC_3PNE9H = "iec-60309-3p-n-e-9h"
497
+ # IEC 60906-1
498
+ TYPE_IEC_60906_1 = "iec-60906-1"
499
+ TYPE_NBR_14136_10A = "nbr-14136-10a"
500
+ TYPE_NBR_14136_20A = "nbr-14136-20a"
485
501
  # NEMA non-locking
486
502
  TYPE_NEMA_115R = "nema-1-15r"
487
503
  TYPE_NEMA_515R = "nema-5-15r"
@@ -592,6 +608,14 @@ class PowerOutletTypeChoices(ChoiceSet):
592
608
  (TYPE_IEC_3PNE9H, "3P+N+E 9H"),
593
609
  ),
594
610
  ),
611
+ (
612
+ "IEC 60906-1",
613
+ (
614
+ (TYPE_IEC_60906_1, "IEC 60906-1"),
615
+ (TYPE_NBR_14136_10A, "2P+T 10A (NBR 14136)"),
616
+ (TYPE_NBR_14136_20A, "2P+T 20A (NBR 14136)"),
617
+ ),
618
+ ),
595
619
  (
596
620
  "NEMA (Non-locking)",
597
621
  (
@@ -750,11 +774,20 @@ class InterfaceTypeChoices(ChoiceSet):
750
774
  TYPE_100GE_CFP4 = "100gbase-x-cfp4"
751
775
  TYPE_100GE_CPAK = "100gbase-x-cpak"
752
776
  TYPE_100GE_QSFP28 = "100gbase-x-qsfp28"
777
+ TYPE_100GE_CXP = "100gbase-x-cxp"
778
+ TYPE_100GE_DSFP = "100gbase-x-dsfp"
779
+ TYPE_100GE_SFP_DD = "100gbase-x-sfpdd"
780
+ TYPE_100GE_QSFP_DD = "100gbase-x-qsfpdd"
753
781
  TYPE_200GE_CFP2 = "200gbase-x-cfp2"
754
782
  TYPE_200GE_QSFP56 = "200gbase-x-qsfp56"
783
+ TYPE_200GE_QSFP_DD = "200gbase-x-qsfpdd"
755
784
  TYPE_400GE_QSFP112 = "400gbase-x-qsfp112"
756
785
  TYPE_400GE_QSFP_DD = "400gbase-x-qsfpdd"
757
786
  TYPE_400GE_OSFP = "400gbase-x-osfp"
787
+ TYPE_400GE_CFP2 = "400gbase-x-cfp2"
788
+ TYPE_400GE_OSFP_RHS = "400gbase-x-osfp-rhs"
789
+ TYPE_400GE_CDFP = "400gbase-x-cdfp"
790
+ TYPE_400GE_CFP8 = "400gbase-x-cfp8"
758
791
  TYPE_800GE_QSFP_DD = "800gbase-x-qsfpdd"
759
792
  TYPE_800GE_OSFP = "800gbase-x-osfp"
760
793
 
@@ -801,7 +834,10 @@ class InterfaceTypeChoices(ChoiceSet):
801
834
  TYPE_8GFC_SFP_PLUS = "8gfc-sfpp"
802
835
  TYPE_16GFC_SFP_PLUS = "16gfc-sfpp"
803
836
  TYPE_32GFC_SFP28 = "32gfc-sfp28"
837
+ TYPE_32GFC_SFP_PLUS = "32gfc-sfpp"
804
838
  TYPE_64GFC_QSFP_PLUS = "64gfc-qsfpp"
839
+ TYPE_64GFC_SFP_DD = "64gfc-sfpdd"
840
+ TYPE_64GFC_SFP_PLUS = "64gfc-sfpp"
805
841
  TYPE_128GFC_QSFP28 = "128gfc-sfp28"
806
842
 
807
843
  # InfiniBand
@@ -909,13 +945,22 @@ class InterfaceTypeChoices(ChoiceSet):
909
945
  (TYPE_100GE_CFP, "CFP (100GE)"),
910
946
  (TYPE_100GE_CFP2, "CFP2 (100GE)"),
911
947
  (TYPE_200GE_CFP2, "CFP2 (200GE)"),
948
+ (TYPE_400GE_CFP2, "CFP2 (400GE)"),
912
949
  (TYPE_100GE_CFP4, "CFP4 (100GE)"),
913
950
  (TYPE_100GE_CPAK, "Cisco CPAK (100GE)"),
914
951
  (TYPE_100GE_QSFP28, "QSFP28 (100GE)"),
952
+ (TYPE_100GE_CXP, "CXP (100GE)"),
953
+ (TYPE_100GE_QSFP_DD, "QSFP-DD (100GE)"),
954
+ (TYPE_100GE_DSFP, "DSFP (100GE)"),
955
+ (TYPE_100GE_SFP_DD, "SFP-DD (100GE)"),
915
956
  (TYPE_200GE_QSFP56, "QSFP56 (200GE)"),
957
+ (TYPE_200GE_QSFP_DD, "QSFP-DD (200GE)"),
916
958
  (TYPE_400GE_QSFP112, "QSFP112 (400GE)"),
917
959
  (TYPE_400GE_QSFP_DD, "QSFP-DD (400GE)"),
918
960
  (TYPE_400GE_OSFP, "OSFP (400GE)"),
961
+ (TYPE_400GE_OSFP_RHS, "OSFP-RHS (400GE)"),
962
+ (TYPE_400GE_CDFP, "CDFP (400GE)"),
963
+ (TYPE_400GE_CFP8, "CPF8 (400GE)"),
919
964
  (TYPE_800GE_QSFP_DD, "QSFP-DD (800GE)"),
920
965
  (TYPE_800GE_OSFP, "OSFP (800GE)"),
921
966
  ),
@@ -977,7 +1022,10 @@ class InterfaceTypeChoices(ChoiceSet):
977
1022
  (TYPE_8GFC_SFP_PLUS, "SFP+ (8GFC)"),
978
1023
  (TYPE_16GFC_SFP_PLUS, "SFP+ (16GFC)"),
979
1024
  (TYPE_32GFC_SFP28, "SFP28 (32GFC)"),
1025
+ (TYPE_32GFC_SFP_PLUS, "SFP+ (32GFC)"),
980
1026
  (TYPE_64GFC_QSFP_PLUS, "QSFP+ (64GFC)"),
1027
+ (TYPE_64GFC_SFP_DD, "SFP-DD (64GFC)"),
1028
+ (TYPE_64GFC_SFP_PLUS, "SFP+ (64GFC)"),
981
1029
  (TYPE_128GFC_QSFP28, "QSFP28 (128GFC)"),
982
1030
  ),
983
1031
  ),
@@ -1124,6 +1172,10 @@ class PortTypeChoices(ChoiceSet):
1124
1172
  TYPE_LSH_PC = "lsh-pc"
1125
1173
  TYPE_LSH_UPC = "lsh-upc"
1126
1174
  TYPE_LSH_APC = "lsh-apc"
1175
+ TYPE_LX5 = "lx5"
1176
+ TYPE_LX5_PC = "lx5-pc"
1177
+ TYPE_LX5_UPC = "lx5-upc"
1178
+ TYPE_LX5_APC = "lx5-apc"
1127
1179
  TYPE_SPLICE = "splice"
1128
1180
  TYPE_CS = "cs"
1129
1181
  TYPE_SN = "sn"
@@ -1170,6 +1222,10 @@ class PortTypeChoices(ChoiceSet):
1170
1222
  (TYPE_LSH_PC, "LSH/PC"),
1171
1223
  (TYPE_LSH_UPC, "LSH/UPC"),
1172
1224
  (TYPE_LSH_APC, "LSH/APC"),
1225
+ (TYPE_LX5, "LX.5"),
1226
+ (TYPE_LX5_PC, "LX.5/PC"),
1227
+ (TYPE_LX5_UPC, "LX.5/UPC"),
1228
+ (TYPE_LX5_APC, "LX.5/APC"),
1173
1229
  (TYPE_MPO, "MPO"),
1174
1230
  (TYPE_MTRJ, "MTRJ"),
1175
1231
  (TYPE_SC, "SC"),
@@ -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,
@@ -89,7 +89,7 @@ menu_items = (
89
89
  ),
90
90
  NavMenuItem(
91
91
  link="dcim:rackreservation_list",
92
- name="Reservations",
92
+ name="Rack Reservations",
93
93
  weight=400,
94
94
  permissions=[
95
95
  "dcim.view_rackreservation",
@@ -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