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
@@ -1,6 +1,7 @@
1
1
  {% extends 'base.html' %}
2
2
  {% load buttons %}
3
3
  {% load helpers %}
4
+ {% load plugins %}
4
5
  {% load static %}
5
6
 
6
7
  {% block header %}
@@ -36,6 +37,7 @@
36
37
  {% block content %}
37
38
  <div class="pull-right noprint">
38
39
  {% block buttons %}{% endblock %}
40
+ {% plugin_buttons content_type.model_class 'list' %}
39
41
  {% if table and request.user.is_authenticated and table_config_form %}
40
42
  <button type="button" class="btn btn-default" data-toggle="modal" data-target="#ObjectTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
41
43
  {% endif %}
@@ -10,53 +10,31 @@ add "&raw" to the end of the URL within a browser.
10
10
  <!DOCTYPE html>
11
11
  <html>
12
12
  <head>
13
+ {% include 'inc/media.html' %}
14
+ {% block extra_styles %}{% endblock %}
13
15
  <!-- Nautobot template requirements -->
14
16
  <title>{% block title %}GraphiQL{% endblock %} - {{ settings.BRANDING_TITLE }}</title>
15
- <link rel="stylesheet"
16
- href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"
17
- onerror="window.location='{% url 'media_failure' %}?filename=bootstrap-3.4.1-dist/css/bootstrap.min.css'">
18
- <link rel="stylesheet"
19
- href="{% static 'materialdesignicons-6.5.95/css/materialdesignicons.min.css' %}"
20
- onerror="window.location='{% url 'media_failure' %}?filename=materialdesignicons-6.5.95/css/materialdesignicons.min.css'">
21
- <link rel="stylesheet"
22
- href="{% static 'jquery-ui-1.13.1/jquery-ui.min.css' %}"
23
- onerror="window.location='{% url 'media_failure' %}?filename=jquery-ui-1.13.1/jquery-ui.min.css'">
24
- <link rel="stylesheet"
25
- href="{% static 'select2-4.0.13/select2.min.css' %}"
26
- onerror="window.location='{% url 'media_failure' %}?filename=select2-4.0.13/select2.min.css'">
27
- <link rel="stylesheet"
28
- href="{% static 'select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css' %}"
29
- onerror="window.location='{% url 'media_failure' %}?filename=select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css'">
30
- <link rel="stylesheet"
31
- href="{% static 'flatpickr-4.6.9/themes/light.min.css' %}"
32
- onerror="window.location='{% url 'media_failure' %}?filename=flatpickr-4.6.9/themes/light.min.css'">
33
- <link rel="stylesheet" id="dark-theme"
34
- href="{% versioned_static 'css/dark.css' %}"
35
- onerror="window.location='{% url 'media_failure' %}?filename=css/dark.css'" disabled="disabled" />
36
- <link rel="stylesheet"
37
- href="{% versioned_static 'css/base.css' %}"
38
- onerror="window.location='{% url 'media_failure' %}?filename=css/base.css'">
39
- <link rel="apple-touch-icon" sizes="180x180" href="{% custom_branding_or_static 'icon_180' 'img/nautobot_icon_180x180.png' %}">
40
- <link rel="icon" type="image/png" sizes="32x32" href="{% custom_branding_or_static 'icon_32' 'img/nautobot_icon_32x32.png' %}">
41
- <link rel="icon" type="image/png" sizes="16x16" href="{% custom_branding_or_static 'icon_16' 'img/nautobot_icon_16x16.png' %}">
42
- <link rel="icon" type="image/png" sizes="192x192" href="{% custom_branding_or_static 'icon_192' 'img/nautobot_icon_192x192.png' %}">
43
- <link rel="mask-icon" type="image/png" color="#0097ff" href="{% custom_branding_or_static 'icon_mask' 'img/nautobot_icon_monochrome.png' %}">
44
- <link rel="shortcut icon" href="{% custom_branding_or_static 'favicon' 'img/favicon.ico' %}">
45
- <meta name="msapplication-TileColor" content="#2d89ef">
46
- <meta name="theme-color" content="#ffffff">
47
- <meta charset="UTF-8">
48
- <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
17
+
49
18
  <!-- GraphiQL template requirements -->
50
19
  <style>
51
- html, body, #editor {
52
- height: 100%;
20
+ #editor {
53
21
  margin: 0;
54
22
  width: 100%;
23
+ height: calc(100vh - 56px - 3rem);
55
24
  }
56
25
  .toolbar .dropdown-menu > li > a {
57
26
  clear: left;
58
27
  margin-right: 120px; /* leave room for the "Save Changes" button if present */
59
28
  }
29
+ .graphiql-container .doc-explorer-title-bar {
30
+ box-sizing: content-box;
31
+ }
32
+ .graphiql-container .history-title-bar {
33
+ box-sizing: content-box;
34
+ }
35
+ .graphiql-container .docExplorerShow {
36
+ white-space: nowrap;
37
+ }
60
38
  </style>
61
39
  <!-- As Nautobot may be run without internet access, we source these files locally rather than from an online CDN -->
62
40
  <link rel="stylesheet"
@@ -72,23 +50,16 @@ add "&raw" to the end of the URL within a browser.
72
50
  onerror="window.location='{% url 'media_failure' %}?filename=graphiql-1.5.16/graphiql.min.js'"></script>
73
51
  <script src="{% static 'subscriptions-transport-ws-0.9.18/client.min.js' %}"
74
52
  onerror="window.location='{% url 'media_failure' %}?filename=subscriptions-transport-ws-0.9.18/client.min.js'"></script>
75
- <!-- Custom CSS to address some conflicts between the two -->
76
- <style>
77
- body {
78
- padding-top: 54px;
79
- }
80
- .graphiql-container .doc-explorer-title-bar {
81
- box-sizing: content-box;
82
- }
83
- </style>
84
53
  </head>
85
54
  <body>
86
55
  <!-- Nautobot page contents -->
87
56
  {% include 'inc/nav_menu.html' %}
88
57
  {% include 'modals/modal_theme.html' with name='theme'%}
89
58
  {% include 'inc/javascript.html' %}
90
- <!-- GraphiQL page contents -->
91
- <div id="editor"></div>
59
+ <div class="container-fluid wrapper" id="main-content">
60
+ <!-- GraphiQL page contents -->
61
+ <div id="editor"></div>
62
+ </div>
92
63
  {% include 'inc/footer.html' %}
93
64
  {% csrf_token %}
94
65
  <script type="application/javascript">
@@ -3,8 +3,8 @@
3
3
  {% load plugins %}
4
4
 
5
5
  <style>
6
- /* Styles for viewports narrower than 1007px */
7
- @media (max-width: 1007px) {
6
+ /* Styles for viewports narrower than 1232px, i.e. bootstrap 'xs' and 'sm' layouts */
7
+ @media (max-width: 1232px) {
8
8
  .footer-col-1, .footer-col-2, .footer-col-3 {
9
9
  text-align: center !important;
10
10
  }
@@ -19,7 +19,7 @@
19
19
  <footer class="footer" id="footer">
20
20
  <div class="container-fluid">
21
21
  <div class="row">
22
- <div class="col-xl-4 col-lg-4 col-md-4 col-sm-3 col-xs-12 text-left footer-col-1 no-wrap">
22
+ <div class="col-xl-4 col-lg-4 col-md-3 col-sm-12 col-xs-12 text-left footer-col-1 no-wrap">
23
23
  {% if request.user.is_authenticated %}
24
24
  <p class="text-muted">
25
25
  {% if settings.BRANDING_FILEPATHS.logo and settings.BRANDING_POWERED_BY_URL %}<a href="{{ settings.BRANDING_POWERED_BY_URL }}"><img src="{% static 'img/nautobot_logo.svg' %}" height="20" /> Powered</a> &middot;{% endif %}
@@ -27,10 +27,10 @@
27
27
  </p>
28
28
  {% endif %}
29
29
  </div>
30
- <div class="col-xl-4 col-lg-4 col-md-4 col-sm-3 col-xs-12 text-center footer-col-2">
30
+ <div class="col-xl-4 col-lg-3 col-md-3 col-sm-12 col-xs-12 text-center footer-col-2">
31
31
  <p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
32
32
  </div>
33
- <div class="col-xl-4 col-lg-4 col-md-4 col-sm-6 col-xs-12 text-right footer-col-3 noprint">
33
+ <div class="col-xl-4 col-lg-5 col-md-6 col-sm-12 col-xs-12 text-right footer-col-3 noprint">
34
34
  {% if request.user.is_authenticated %}
35
35
  <p class="text-muted footer-links">
36
36
  <a href="#theme_modal" data-toggle="modal" data-target="#theme_modal" id="btn-theme-modal"><i class="mdi mdi-theme-light-dark text-primary"></i>Theme</a> &middot;
@@ -121,9 +121,6 @@
121
121
  document.addEventListener('DOMContentLoaded', function() {
122
122
  const navbar = document.querySelector('.navbar-fixed-left');
123
123
  const navbarHeader = document.querySelector('.navbar-header');
124
- const breadcrumb = document.querySelector('.breadcrumb');
125
- const bannerAlertArea = document.querySelector('.banner-alert-area');
126
- const bannerAlert = document.querySelector('.plugin-banner') !== null;
127
124
  const mainContent = document.querySelector('#main-content');
128
125
  const footer = document.querySelector('#footer');
129
126
  const dropdownToggles = document.querySelectorAll('.navbar-fixed-left .navbar-nav > .dropdown > a[data-toggle="collapse"]');
@@ -180,10 +177,6 @@
180
177
  function adjustElementsForNavbarState(isCollapsed) {
181
178
  const marginLeftValue = isCollapsed ? '-240px' : '0px';
182
179
  mainContent.style.marginLeft = marginLeftValue;
183
- bannerAlertArea.classList.toggle('collapsed', isCollapsed);
184
- if(!bannerAlert && breadcrumb) {
185
- breadcrumb.classList.toggle('collapsed', isCollapsed);
186
- };
187
180
  if(footer) footer.style.marginLeft = marginLeftValue;
188
181
  toggler.style.left = isCollapsed ? '-5px' : '225px';
189
182
  }
@@ -37,6 +37,12 @@ from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
37
37
  # Number of seconds to cache ContentType lookups. Set to 0 to disable caching.
38
38
  # CONTENT_TYPE_CACHE_TIMEOUT = int(os.getenv("NAUTOBOT_CONTENT_TYPE_CACHE_TIMEOUT", "0"))
39
39
 
40
+ # Celery Beat heartbeat file path - will be touched by Beat each time it wakes up as a proof-of-health.
41
+ # CELERY_BEAT_HEARTBEAT_FILE = os.getenv(
42
+ # "NAUTOBOT_CELERY_BEAT_HEARTBEAT_FILE",
43
+ # os.path.join(tempfile.gettempdir(), "nautobot_celery_beat_heartbeat"),
44
+ # )
45
+
40
46
  # Celery broker URL used to tell workers where queues are located
41
47
  #
42
48
  # CELERY_BROKER_URL = os.getenv("NAUTOBOT_CELERY_BROKER_URL", parse_redis_connection(redis_database=0))
@@ -7,13 +7,20 @@
7
7
  {% include 'inc/media.html' %}
8
8
  {% endblock bootstrap_theme %}
9
9
 
10
- {% block navbar %}
11
- {% include 'inc/nav_menu.html' %}
12
- {% endblock navbar %}
13
-
14
10
  {% block body %}
15
- {{ block.super }}
11
+ {% include 'inc/nav_menu.html' %}
12
+ <div class="container-fluid" id="main-content">
13
+ {{ block.super }}
14
+ </div>
16
15
  {% include 'inc/footer.html' %}
16
+ <style>
17
+ .navbar-static-top {
18
+ display: none;
19
+ }
20
+ #main-content {
21
+ min-height: calc(100vh - 70px);
22
+ }
23
+ </style>
17
24
  {% endblock body %}
18
25
 
19
26
  {% block script %}
@@ -4,6 +4,7 @@ import warnings
4
4
  from django.apps import apps
5
5
  from django.contrib.auth import get_user_model
6
6
  from django.contrib.contenttypes.models import ContentType
7
+ from django.core.cache import cache
7
8
  from django.core.exceptions import FieldDoesNotExist
8
9
  from django.db.models import JSONField, ManyToManyField
9
10
  from django.forms.models import model_to_dict
@@ -13,7 +14,7 @@ from rest_framework.test import APIClient, APIRequestFactory
13
14
  from nautobot.core import testing
14
15
  from nautobot.core.models import fields as core_fields
15
16
  from nautobot.core.utils import permissions
16
- from nautobot.extras import management, models as extras_models, signals as extras_signals
17
+ from nautobot.extras import management, models as extras_models
17
18
  from nautobot.users import models as users_models
18
19
 
19
20
  # Use the proper swappable User model
@@ -63,11 +64,18 @@ class NautobotTestCaseMixin:
63
64
  self.client.force_login(self.user)
64
65
 
65
66
  def tearDown(self):
66
- """Clear lru_cache data to avoid leakage of information between test cases when running in parallel."""
67
- extras_signals.invalidate_lru_cache(extras_models.CustomField)
68
- extras_signals.invalidate_lru_cache(extras_models.ComputedField)
69
- extras_signals.invalidate_lru_cache(extras_models.Relationship)
67
+ """
68
+ Clear cache after each test case.
69
+
70
+ In theory this shouldn't be necessary as our cache **should** appropriately update and clear itself when
71
+ data changes occur, but in practice we've seen issues here. Best guess at present is that it's due to
72
+ `TransactionTestCase` truncating the database, which presumably doesn't trigger the relevant Django signals
73
+ that would otherwise refresh the cache appropriately.
74
+
75
+ See also: https://code.djangoproject.com/ticket/11505
76
+ """
70
77
  super().tearDown()
78
+ cache.clear()
71
79
 
72
80
  def prepare_instance(self, instance):
73
81
  """
@@ -15,47 +15,33 @@ class PluginNavBarTestCase(SeleniumTestCase):
15
15
 
16
16
  fixtures = ["user-data.json"]
17
17
  navbar = {
18
- "Example Menu": {
19
- "Example Group 1": {
20
- "Example Model": {
21
- "permission": "example_plugin.view_examplemodel",
22
- "buttons": ["Add"],
23
- },
24
- },
25
- },
26
18
  "Circuits": {
27
19
  "Circuits": {
28
20
  "Circuits": {
29
- "permission": "circuits.view_circuit",
30
21
  "buttons": ["Add"],
31
22
  },
32
23
  "Circuit Types": {
33
- "permission": "circuits.view_circuittype",
34
24
  "buttons": ["Add"],
35
25
  },
36
26
  },
37
27
  "Example Circuit Group": {
38
- "Example Model": {
39
- "permission": "example_plugin.view_examplemodel",
28
+ "Example Models": {
40
29
  "buttons": ["Add"],
41
30
  },
42
31
  },
43
32
  "Providers": {
44
33
  "Providers": {
45
- "permission": "circuits.view_provider",
46
34
  "buttons": ["Add"],
47
35
  },
48
36
  },
49
37
  },
50
38
  "Plugins": {
51
39
  "Example Nautobot App": {
52
- "Models": {
53
- "permission": "example_plugin.view_examplemodel",
54
- "buttons": ["Add a new example model"],
40
+ "Example Models": {
41
+ "buttons": ["Add"],
55
42
  },
56
- "Other Models": {
57
- "permission": "example_plugin.view_examplemodel",
58
- "buttons": [],
43
+ "Another Example Models": {
44
+ "buttons": ["Add"],
59
45
  },
60
46
  },
61
47
  },
@@ -87,7 +73,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
87
73
 
88
74
  group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='Example Group 1']")
89
75
 
90
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='Example Model']]"
76
+ item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='Example Models']]"
91
77
  group.find_by_xpath(item_xpath)
92
78
 
93
79
  def test_plugin_navbar_modify_circuits(self):
@@ -147,7 +133,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
147
133
  for button_name in item_details["buttons"]:
148
134
  button = item.find_by_xpath(f"{item_xpath}/div//a[@data-original-title='{button_name}']")
149
135
  if button_class := getattr(ButtonActionColorChoices, button_name.upper(), None):
150
- self.assertIn(button_class, button.get_attribute("class"))
136
+ self.assertIn(button_class, button["class"])
151
137
  if button_icon := getattr(ButtonActionIconChoices, button_name.upper(), None):
152
138
  icon = button.find_by_xpath(f"{item_xpath}/div//a[@data-original-title='{button_name}']/i")
153
139
  self.assertIn(button_icon, icon["class"])
@@ -0,0 +1,67 @@
1
+ from django.test import tag
2
+
3
+ from nautobot.core.testing import TestCase
4
+ from nautobot.core.utils.lookup import get_url_for_url_pattern, get_url_patterns
5
+
6
+
7
+ @tag("integration")
8
+ class AuthenticationEnforcedTestCase(TestCase):
9
+ r"""
10
+ Test that all\* registered views require authentication to access.
11
+
12
+ \* with a very small number of known exceptions such as login and logout views.
13
+ """
14
+
15
+ def test_all_views_require_authentication(self):
16
+ self.client.logout()
17
+ url_patterns = get_url_patterns()
18
+
19
+ for url_pattern in url_patterns:
20
+ with self.subTest(url_pattern=url_pattern):
21
+ url = get_url_for_url_pattern(url_pattern)
22
+ response = self.client.get(url, follow=True)
23
+
24
+ if response.status_code == 405: # Method not allowed
25
+ response = self.client.post(url, follow=True)
26
+
27
+ # Is a view that *should* be open to unauthenticated users?
28
+ if url in [
29
+ "/admin/login/",
30
+ "/api/plugins/example-plugin/webhook/",
31
+ "/health/",
32
+ "/login/",
33
+ "/media-failure/",
34
+ "/template.css",
35
+ ]:
36
+ self.assertHttpStatus(response, 200, msg=url)
37
+ elif response.status_code == 200:
38
+ # UI views generally should redirect unauthenticated users to the appropriate login page
39
+ if url.startswith("/admin"):
40
+ if "logout" in url:
41
+ # /admin/logout/ sets next=/admin/ because having login redirect to logout would be silly
42
+ redirect_url = "/admin/login/?next=/admin/"
43
+ else:
44
+ redirect_url = f"/admin/login/?next={url}"
45
+ else:
46
+ if "logout" in url:
47
+ # /logout/ sets next=/ because having login redirect back to logout would be silly
48
+ redirect_url = "/login/?next=/"
49
+ else:
50
+ redirect_url = f"/login/?next={url}"
51
+ self.assertRedirects(response, redirect_url)
52
+ elif response.status_code != 403:
53
+ if any(
54
+ url.startswith(path)
55
+ for path in [
56
+ "/complete/", # social auth
57
+ "/login/", # social auth
58
+ "/media/", # MEDIA_ROOT
59
+ "/plugins/example-plugin/docs/", # STATIC_ROOT
60
+ ]
61
+ ):
62
+ self.assertEqual(response.status_code, 404)
63
+ else:
64
+ self.fail(
65
+ f"Unexpected {response.status_code} response at {url}: "
66
+ + response.content.decode(response.charset)
67
+ )
@@ -1,11 +1,32 @@
1
+ import copy
2
+
1
3
  from django.conf import settings
2
4
  from django.core.management import call_command
3
5
  from django.db import connections
4
- from django.test.runner import DiscoverRunner
5
- from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper
6
+ from django.test.runner import _init_worker, DiscoverRunner, ParallelTestSuite
7
+ from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper, override_settings
6
8
  import yaml
7
9
 
8
10
  from nautobot.core.celery import app, setup_nautobot_job_logging
11
+ from nautobot.core.settings_funcs import parse_redis_connection
12
+
13
+
14
+ def init_worker_with_unique_cache(counter):
15
+ """Extend Django's default parallel unit test setup to also ensure distinct Redis caches."""
16
+ _init_worker(counter) # call Django default to set _worker_id and set up parallel DB instances
17
+ # _worker_id is now 1, 2, 3, 4, etc.
18
+
19
+ from django.test.runner import _worker_id
20
+
21
+ # Redis DB indices 0 and 1 are used by non-automated testing, so we want to start at index 2
22
+ caches = copy.deepcopy(settings.CACHES)
23
+ caches["default"]["LOCATION"] = parse_redis_connection(redis_database=_worker_id + 1)
24
+ override_settings(CACHES=caches).enable()
25
+ print(f"Set settings.CACHES['default']['LOCATION'] to use Redis index {_worker_id + 1}")
26
+
27
+
28
+ class NautobotParallelTestSuite(ParallelTestSuite):
29
+ init_worker = init_worker_with_unique_cache
9
30
 
10
31
 
11
32
  class NautobotTestRunner(DiscoverRunner):
@@ -22,6 +43,8 @@ class NautobotTestRunner(DiscoverRunner):
22
43
  Only integration tests that DO NOT inherit from `SeleniumTestCase` will need to be explicitly tagged.
23
44
  """
24
45
 
46
+ parallel_test_suite = NautobotParallelTestSuite
47
+
25
48
  exclude_tags = ["integration"]
26
49
 
27
50
  def __init__(self, cache_test_fixtures=False, **kwargs):
@@ -630,21 +630,9 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
630
630
  self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
631
631
 
632
632
  def test_graphql_api_no_token(self):
633
- """Validate unauthenticated users are not able to query anything by default."""
633
+ """Validate unauthenticated users are not able to query anything."""
634
634
  response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
635
- self.assertEqual(response.status_code, status.HTTP_200_OK)
636
- self.assertIsInstance(response.data["data"]["racks"], list)
637
- names = [item["name"] for item in response.data["data"]["racks"]]
638
- self.assertEqual(names, [])
639
-
640
- @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
641
- def test_graphql_api_no_token_exempt(self):
642
- """Validate unauthenticated users are able to query based on the exempt permissions."""
643
- response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
644
- self.assertEqual(response.status_code, status.HTTP_200_OK)
645
- self.assertIsInstance(response.data["data"]["racks"], list)
646
- names = [item["name"] for item in response.data["data"]["racks"]]
647
- self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
635
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
648
636
 
649
637
  def test_graphql_api_wrong_token(self):
650
638
  """Validate a wrong token return 403."""
@@ -113,7 +113,7 @@ class NaturalKeyTestCase(BaseModelTest):
113
113
  """
114
114
 
115
115
  # Ensure the cache is empty from previous tests
116
- cache.delete(f"{self.FakeBaseModel._meta.label_lower}._content_type")
116
+ cache.delete(self.FakeBaseModel._content_type_cache_key)
117
117
 
118
118
  with patch.object(self.FakeBaseModel, "_content_type", return_value=True) as mock__content_type:
119
119
  self.FakeBaseModel._content_type_cached
@@ -127,7 +127,7 @@ class NaturalKeyTestCase(BaseModelTest):
127
127
  self.assertEqual(mock__content_type.call_count, 2)
128
128
 
129
129
  # Clean-up after ourselves
130
- cache.delete(f"{self.FakeBaseModel._meta.label_lower}._content_type")
130
+ cache.delete(self.FakeBaseModel._content_type_cache_key)
131
131
 
132
132
  @override_settings(CONTENT_TYPE_CACHE_TIMEOUT=0)
133
133
  def test__content_type_caching_disabled(self):
@@ -136,7 +136,7 @@ class NaturalKeyTestCase(BaseModelTest):
136
136
  """
137
137
 
138
138
  # Ensure the cache is empty from previous tests
139
- cache.delete(f"{self.FakeBaseModel._meta.label_lower}._content_type")
139
+ cache.delete(self.FakeBaseModel._content_type_cache_key)
140
140
 
141
141
  with patch.object(self.FakeBaseModel, "_content_type", return_value=True) as mock__content_type:
142
142
  self.FakeBaseModel._content_type_cached
@@ -1,13 +1,72 @@
1
1
  from unittest.mock import patch
2
2
 
3
- from django.test import TestCase
3
+ from django.test import tag, TestCase
4
+ from django.urls import resolve
4
5
 
5
6
  from nautobot.core.apps import NAV_CONTEXT_NAMES, NavContext, NavGrouping, NavItem, register_new_ui_menu_items
7
+ from nautobot.core.choices import ButtonActionColorChoices, ButtonActionIconChoices
8
+ from nautobot.core.templatetags.helpers import bettertitle
9
+ from nautobot.core.utils.lookup import get_route_for_model
10
+ from nautobot.core.utils.permissions import get_permission_for_model
11
+ from nautobot.extras.registry import registry
6
12
 
7
13
 
8
- # TODO(timizuo): Here might not be the best place to add this test class
14
+ @tag("unit")
15
+ class NavMenuTestCase(TestCase):
16
+ """Verify correct construction of the nav menu."""
17
+
18
+ def test_menu_item_attributes(self):
19
+ """Verify that menu items and buttons have the correct text and expected permissions."""
20
+ for tab in registry["nav_menu"]["tabs"]:
21
+ for group in registry["nav_menu"]["tabs"][tab]["groups"]:
22
+ for item_url, item_details in registry["nav_menu"]["tabs"][tab]["groups"][group]["items"].items():
23
+ with self.subTest(f"{tab} > {group} > {item_url}"):
24
+ view_func = resolve(item_url).func
25
+ try:
26
+ # NautobotUIViewSet
27
+ view_class = view_func.view_class
28
+ except AttributeError:
29
+ # ObjectListView
30
+ view_class = view_func.cls
31
+ try:
32
+ view_queryset = view_class.queryset
33
+ view_model = view_queryset.model
34
+
35
+ if item_details["name"] not in {
36
+ "Elevations",
37
+ "Interface Connections",
38
+ "Console Connections",
39
+ "Power Connections",
40
+ "Job Approval Queue",
41
+ }:
42
+ expected_name = bettertitle(view_model._meta.verbose_name_plural)
43
+ if expected_name == "VM Interfaces":
44
+ expected_name = "Interfaces"
45
+ elif expected_name == "Object Changes":
46
+ expected_name = "Change Log"
47
+ self.assertEqual(item_details["name"], expected_name)
48
+ if item_url == get_route_for_model(view_model, "list"):
49
+ # Not assertEqual as some menu items have additional permissions defined.
50
+ self.assertIn(get_permission_for_model(view_model, "view"), item_details["permissions"])
51
+ except AttributeError:
52
+ # Not a model view?
53
+ self.assertIn(item_details["name"], {"Installed Plugins", "Interface Connections"})
54
+
55
+ for button, button_details in item_details["buttons"].items():
56
+ with self.subTest(f"{tab} > {group} > {item_url} > {button}"):
57
+ # Currently all core menu items should have just a single Add button
58
+ self.assertEqual(button, "Add")
59
+ self.assertEqual(
60
+ button_details["permissions"], {get_permission_for_model(view_model, "add")}
61
+ )
62
+ self.assertEqual(button_details["link"], get_route_for_model(view_model, "add"))
63
+ self.assertEqual(button_details["button_class"], ButtonActionColorChoices.ADD)
64
+ self.assertEqual(button_details["icon_class"], ButtonActionIconChoices.ADD)
65
+
66
+
67
+ @tag("unit")
9
68
  class NewUINavTest(TestCase):
10
- @patch("nautobot.core.apps.registry", {"new_ui_nav_menu": {}})
69
+ @patch.dict(registry, values={"new_ui_nav_menu": {}}, clear=True)
11
70
  def test_build_new_ui_nav_menu(self):
12
71
  """Assert building and adding of new ui nav to registry
13
72
 
@@ -15,8 +74,6 @@ class NewUINavTest(TestCase):
15
74
  1. New UI nav is added to registry["new_ui_nav_menu"]
16
75
  2. registry["new_ui_nav_menu"] is sorted by weight
17
76
  """
18
- from nautobot.core.apps import registry # Import here cause of the mock patch
19
-
20
77
  # Test App 1
21
78
  navigation_1 = (
22
79
  NavContext(
@@ -32,7 +89,7 @@ class NewUINavTest(TestCase):
32
89
  NavGrouping(
33
90
  name="App 1 Inventory Group 2",
34
91
  items=(
35
- NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_status"]),
92
+ NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_role"]),
36
93
  NavItem(name="Menu 2", link="extras:tag_list"),
37
94
  ),
38
95
  ),
@@ -62,7 +119,7 @@ class NewUINavTest(TestCase):
62
119
  items=(
63
120
  NavItem(name="Tags", link="extras:tag_list"),
64
121
  NavItem(name="Location", link="dcim:location_list"),
65
- NavItem(name="Roles", link="extras:role_list", permissions=["extras.view_status"]),
122
+ NavItem(name="Roles", link="extras:role_list", permissions=["extras.view_role"]),
66
123
  ),
67
124
  ),
68
125
  ),
@@ -100,7 +157,7 @@ class NewUINavTest(TestCase):
100
157
  "Menu 1": {
101
158
  "name": "Menu 1",
102
159
  "weight": 1000,
103
- "permissions": ["extras.view_status"],
160
+ "permissions": ["extras.view_role"],
104
161
  "data": "/extras/roles/",
105
162
  },
106
163
  "Menu 2": {
@@ -157,7 +214,7 @@ class NewUINavTest(TestCase):
157
214
  "Roles": {
158
215
  "name": "Roles",
159
216
  "weight": 1000,
160
- "permissions": ["extras.view_status"],
217
+ "permissions": ["extras.view_role"],
161
218
  "data": "/extras/roles/",
162
219
  },
163
220
  },
@@ -171,7 +228,7 @@ class NewUINavTest(TestCase):
171
228
  def test_validation_in_new_ui_navigation_classes(self):
172
229
  """Test Validation on each of the new ui navigation classes `NavItem`, `NavGrouping`, `NavContext`"""
173
230
 
174
- nav_item_1 = NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_status"])
231
+ nav_item_1 = NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_role"])
175
232
  nav_item_2 = NavItem(name="Menu 2", link="invalid_url")
176
233
  self.assertEqual(
177
234
  nav_item_1.initial_dict,
@@ -105,7 +105,9 @@ class GetReleasesTestCase(SimpleTestCase):
105
105
  # Check if result is put in cache
106
106
  expected_version_str, expected_url = max(releases)
107
107
  expected_version = version.parse(expected_version_str)
108
- mock_cache_set.assert_called_once_with("latest_release", (expected_version, expected_url), 160876)
108
+ mock_cache_set.assert_called_once_with(
109
+ "nautobot.core.releases.get_latest_release", (expected_version, expected_url), 160876
110
+ )
109
111
 
110
112
  @patch.object(requests, "get")
111
113
  @patch.object(cache, "set")
@@ -142,7 +144,9 @@ class GetReleasesTestCase(SimpleTestCase):
142
144
  # Check if result is put in cache
143
145
  expected_version_str, expected_url = max(releases)
144
146
  expected_version = version.parse(expected_version_str)
145
- mock_cache_set.assert_called_once_with("latest_release", (expected_version, expected_url), 160876)
147
+ mock_cache_set.assert_called_once_with(
148
+ "nautobot.core.releases.get_latest_release", (expected_version, expected_url), 160876
149
+ )
146
150
 
147
151
  @patch.object(requests, "get")
148
152
  @patch.object(cache, "set")
@@ -172,7 +176,9 @@ class GetReleasesTestCase(SimpleTestCase):
172
176
  )
173
177
 
174
178
  # Check if failure is put in cache
175
- mock_cache_set.assert_called_once_with("latest_release_no_retry", "https://localhost/unittest/releases", 900)
179
+ mock_cache_set.assert_called_once_with(
180
+ "nautobot.core.releases.get_releases.no_retry", "https://localhost/unittest/releases", 900
181
+ )
176
182
 
177
183
  @patch.object(requests, "get")
178
184
  @patch.object(cache, "set")