nautobot 2.0.5__py3-none-any.whl → 2.1.0b1__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 (376) hide show
  1. nautobot/circuits/navigation.py +0 -25
  2. nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -9
  3. nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
  4. nautobot/circuits/tests/test_filters.py +1 -0
  5. nautobot/core/api/serializers.py +15 -5
  6. nautobot/core/api/views.py +18 -19
  7. nautobot/core/choices.py +1 -1
  8. nautobot/core/filters.py +12 -4
  9. nautobot/core/jobs/__init__.py +125 -3
  10. nautobot/core/management/commands/generate_test_data.py +4 -1
  11. nautobot/core/models/fields.py +12 -2
  12. nautobot/core/settings.py +8 -7
  13. nautobot/core/templates/base_django.html +2 -2
  14. nautobot/core/templates/buttons/export.html +57 -30
  15. nautobot/core/templates/generic/object_list.html +2 -2
  16. nautobot/core/templates/generic/object_retrieve.html +8 -1
  17. nautobot/core/templates/home.html +5 -5
  18. nautobot/core/templates/inc/created_updated.html +2 -2
  19. nautobot/core/templates/inc/footer.html +2 -2
  20. nautobot/core/templates/inc/javascript.html +0 -10
  21. nautobot/core/templates/inc/media.html +2 -0
  22. nautobot/core/templates/inc/nav_menu.html +66 -68
  23. nautobot/core/templates/inc/object_details_advanced_panel.html +19 -0
  24. nautobot/core/templates/nautobot_config.py.j2 +10 -4
  25. nautobot/core/templates/panel_table.html +1 -1
  26. nautobot/core/templates/template.css +89 -0
  27. nautobot/core/templates/utilities/templatetags/table_config_form.html +1 -0
  28. nautobot/core/templatetags/buttons.py +7 -2
  29. nautobot/core/testing/views.py +34 -4
  30. nautobot/core/tests/integration/test_home.py +1 -43
  31. nautobot/core/tests/integration/test_navbar.py +10 -64
  32. nautobot/core/tests/integration/test_plugin_home.py +4 -5
  33. nautobot/core/tests/integration/test_plugin_navbar.py +20 -16
  34. nautobot/core/tests/integration/test_theme.py +4 -0
  35. nautobot/core/tests/test_api.py +14 -66
  36. nautobot/core/tests/test_filters.py +127 -0
  37. nautobot/core/tests/test_forms.py +1 -1
  38. nautobot/core/tests/test_graphql.py +165 -2
  39. nautobot/core/tests/test_jobs.py +112 -0
  40. nautobot/core/tests/test_openapi.py +6 -0
  41. nautobot/core/tests/test_views.py +11 -85
  42. nautobot/core/urls.py +6 -1
  43. nautobot/core/utils/lookup.py +28 -0
  44. nautobot/core/utils/requests.py +2 -3
  45. nautobot/core/views/__init__.py +3 -4
  46. nautobot/core/views/generic.py +9 -4
  47. nautobot/core/views/mixins.py +4 -2
  48. nautobot/core/views/renderers.py +5 -0
  49. nautobot/dcim/models/device_components.py +1 -0
  50. nautobot/dcim/navigation.py +10 -165
  51. nautobot/dcim/templates/dcim/location.html +1 -1
  52. nautobot/dcim/tests/features/locations.feature +143 -0
  53. nautobot/dcim/tests/test_api.py +1 -1
  54. nautobot/dcim/tests/test_filters.py +11 -3
  55. nautobot/extras/admin.py +1 -1
  56. nautobot/extras/api/serializers.py +33 -0
  57. nautobot/extras/api/urls.py +6 -0
  58. nautobot/extras/api/views.py +45 -6
  59. nautobot/extras/factory.py +28 -2
  60. nautobot/extras/filters/__init__.py +52 -0
  61. nautobot/extras/filters/mixins.py +4 -29
  62. nautobot/extras/forms/forms.py +43 -0
  63. nautobot/extras/jobs.py +31 -9
  64. nautobot/extras/migrations/0100_fileproxy_job_result.py +32 -0
  65. nautobot/extras/migrations/0101_externalintegration.py +61 -0
  66. nautobot/extras/migrations/0102_set_null_objectchange_contenttype.py +32 -0
  67. nautobot/extras/models/__init__.py +2 -0
  68. nautobot/extras/models/change_logging.py +2 -2
  69. nautobot/extras/models/models.py +96 -16
  70. nautobot/extras/navigation.py +17 -29
  71. nautobot/extras/signals.py +15 -0
  72. nautobot/extras/tables.py +27 -0
  73. nautobot/extras/templates/extras/externalintegration_retrieve.html +37 -0
  74. nautobot/extras/templates/extras/inc/jobresult.html +24 -0
  75. nautobot/extras/templates/extras/jobresult.html +24 -0
  76. nautobot/extras/test_jobs/file_output.py +16 -0
  77. nautobot/extras/tests/test_api.py +92 -0
  78. nautobot/extras/tests/test_filters.py +64 -2
  79. nautobot/extras/tests/test_jobs.py +39 -0
  80. nautobot/extras/tests/test_models.py +34 -0
  81. nautobot/extras/tests/test_views.py +22 -2
  82. nautobot/extras/urls.py +1 -0
  83. nautobot/extras/views.py +15 -0
  84. nautobot/ipam/forms.py +16 -0
  85. nautobot/ipam/models.py +3 -0
  86. nautobot/ipam/navigation.py +2 -59
  87. nautobot/ipam/templates/ipam/ipaddress.html +0 -9
  88. nautobot/ipam/templates/ipam/prefix.html +0 -9
  89. nautobot/ipam/tests/features/prefixes.feature +134 -0
  90. nautobot/ipam/tests/test_filters.py +5 -10
  91. nautobot/ipam/tests/test_views.py +8 -1
  92. nautobot/ipam/views.py +3 -0
  93. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +191 -191
  94. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  95. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +1 -1
  96. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  97. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +874 -881
  98. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  99. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  100. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  101. nautobot/project-static/css/base.css +135 -99
  102. nautobot/project-static/css/dark.css +65 -6
  103. nautobot/project-static/docs/404.html +44 -16
  104. nautobot/project-static/docs/apps/index.html +44 -16
  105. nautobot/project-static/docs/apps/nautobot-apps.html +44 -16
  106. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +44 -16
  107. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +44 -16
  108. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1597 -1457
  109. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +44 -16
  110. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +45 -17
  111. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +44 -16
  112. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +44 -16
  113. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +44 -16
  114. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +44 -16
  115. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +353 -432
  116. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +66 -38
  117. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +44 -16
  118. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2154 -2307
  119. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +807 -691
  120. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +44 -16
  121. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +44 -16
  122. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +58 -30
  123. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +3600 -3456
  124. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +44 -16
  125. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +44 -16
  126. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +101 -75
  127. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +3083 -3019
  128. nautobot/project-static/docs/development/apps/api/configuration-view.html +44 -16
  129. nautobot/project-static/docs/development/apps/api/database-backend-config.html +44 -16
  130. nautobot/project-static/docs/development/apps/api/models/django-admin.html +44 -16
  131. nautobot/project-static/docs/development/apps/api/models/global-search.html +44 -16
  132. nautobot/project-static/docs/development/apps/api/models/graphql.html +44 -16
  133. nautobot/project-static/docs/development/apps/api/models/index.html +44 -16
  134. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +44 -16
  135. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +44 -16
  136. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +44 -16
  137. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +44 -16
  138. nautobot/project-static/docs/development/apps/api/platform-features/index.html +44 -16
  139. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +44 -16
  140. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +44 -16
  141. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +44 -16
  142. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +44 -16
  143. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +44 -16
  144. nautobot/project-static/docs/development/apps/api/prometheus.html +44 -16
  145. nautobot/project-static/docs/development/apps/api/setup.html +44 -16
  146. nautobot/project-static/docs/development/apps/api/testing.html +44 -16
  147. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +44 -16
  148. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +44 -16
  149. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +44 -16
  150. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +44 -16
  151. nautobot/project-static/docs/development/apps/api/ui-extensions/object-detail-views.html +44 -16
  152. nautobot/project-static/docs/development/apps/api/ui-extensions/tabs.html +44 -16
  153. nautobot/project-static/docs/development/apps/api/views/base-template.html +44 -16
  154. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +44 -16
  155. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +44 -16
  156. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +44 -16
  157. nautobot/project-static/docs/development/apps/api/views/index.html +44 -16
  158. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +44 -16
  159. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +44 -16
  160. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +44 -16
  161. nautobot/project-static/docs/development/apps/api/views/notes.html +44 -16
  162. nautobot/project-static/docs/development/apps/api/views/rest-api.html +44 -16
  163. nautobot/project-static/docs/development/apps/api/views/urls.html +44 -16
  164. nautobot/project-static/docs/development/apps/api/views/view-overrides.html +44 -16
  165. nautobot/project-static/docs/development/apps/index.html +44 -16
  166. nautobot/project-static/docs/development/apps/migration/code-updates.html +44 -16
  167. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +44 -16
  168. nautobot/project-static/docs/development/apps/migration/from-v1.html +44 -16
  169. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +44 -16
  170. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +44 -16
  171. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +44 -16
  172. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +44 -16
  173. nautobot/project-static/docs/development/apps/porting-from-netbox.html +44 -16
  174. nautobot/project-static/docs/development/core/application-registry.html +44 -16
  175. nautobot/project-static/docs/development/core/best-practices.html +44 -16
  176. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +44 -16
  177. nautobot/project-static/docs/development/core/extending-models.html +44 -16
  178. nautobot/project-static/docs/development/core/generic-views.html +44 -16
  179. nautobot/project-static/docs/development/core/getting-started.html +44 -16
  180. nautobot/project-static/docs/development/core/homepage.html +44 -16
  181. nautobot/project-static/docs/development/core/index.html +44 -16
  182. nautobot/project-static/docs/development/core/model-features.html +44 -16
  183. nautobot/project-static/docs/development/core/natural-keys.html +44 -16
  184. nautobot/project-static/docs/development/core/navigation-menu.html +44 -21
  185. nautobot/project-static/docs/development/core/react-ui.html +44 -16
  186. nautobot/project-static/docs/development/core/release-checklist.html +44 -16
  187. nautobot/project-static/docs/development/core/role-internals.html +44 -16
  188. nautobot/project-static/docs/development/core/style-guide.html +44 -16
  189. nautobot/project-static/docs/development/core/templates.html +44 -16
  190. nautobot/project-static/docs/development/core/testing.html +44 -16
  191. nautobot/project-static/docs/development/core/user-preferences.html +44 -16
  192. nautobot/project-static/docs/development/index.html +44 -16
  193. nautobot/project-static/docs/development/jobs/index.html +280 -234
  194. nautobot/project-static/docs/development/jobs/migration/from-v1.html +44 -16
  195. nautobot/project-static/docs/index.html +44 -16
  196. nautobot/project-static/docs/objects.inv +0 -0
  197. nautobot/project-static/docs/release-notes/index.html +47 -19
  198. nautobot/project-static/docs/release-notes/version-1.0.html +44 -16
  199. nautobot/project-static/docs/release-notes/version-1.1.html +44 -16
  200. nautobot/project-static/docs/release-notes/version-1.2.html +44 -16
  201. nautobot/project-static/docs/release-notes/version-1.3.html +44 -16
  202. nautobot/project-static/docs/release-notes/version-1.4.html +44 -16
  203. nautobot/project-static/docs/release-notes/version-1.5.html +44 -16
  204. nautobot/project-static/docs/release-notes/version-1.6.html +44 -16
  205. nautobot/project-static/docs/release-notes/version-2.0.html +47 -19
  206. nautobot/project-static/docs/release-notes/version-2.1.html +5724 -0
  207. nautobot/project-static/docs/search/search_index.json +1 -1
  208. nautobot/project-static/docs/sitemap.xml +247 -237
  209. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  210. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +44 -16
  211. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +44 -16
  212. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +44 -16
  213. nautobot/project-static/docs/user-guide/administration/configuration/index.html +44 -16
  214. nautobot/project-static/docs/user-guide/administration/configuration/node-configuration.html +44 -16
  215. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +109 -43
  216. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +44 -16
  217. nautobot/project-static/docs/user-guide/administration/guides/caching.html +44 -16
  218. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +44 -16
  219. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +44 -16
  220. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +44 -16
  221. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +44 -16
  222. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +44 -16
  223. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -19
  224. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +44 -16
  225. nautobot/project-static/docs/user-guide/administration/installation/docker.html +44 -16
  226. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +44 -16
  227. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +44 -16
  228. nautobot/project-static/docs/user-guide/administration/installation/index.html +44 -16
  229. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +44 -16
  230. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +44 -16
  231. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +44 -16
  232. nautobot/project-static/docs/user-guide/administration/installation/services.html +44 -16
  233. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +44 -16
  234. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +44 -16
  235. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +44 -16
  236. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +44 -16
  237. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +44 -16
  238. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +44 -16
  239. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +44 -16
  240. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +44 -16
  241. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +44 -16
  242. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +44 -16
  243. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +44 -16
  244. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +44 -16
  245. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +44 -16
  246. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +44 -16
  247. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +44 -16
  248. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +44 -16
  249. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +44 -16
  250. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +44 -16
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +44 -16
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +44 -16
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +44 -16
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +44 -16
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +44 -16
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +44 -16
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +44 -16
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +44 -16
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +44 -16
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +44 -16
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +44 -16
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +44 -16
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +44 -16
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +44 -16
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +44 -16
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +44 -16
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +44 -16
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +44 -16
  269. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +44 -16
  270. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +44 -16
  271. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +44 -16
  272. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +44 -16
  273. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +44 -16
  274. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +44 -16
  275. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +44 -16
  276. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +44 -16
  277. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +44 -16
  278. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +44 -16
  279. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +44 -16
  280. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +44 -16
  281. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +44 -16
  282. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +44 -16
  283. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +44 -16
  284. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +44 -16
  285. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +44 -16
  286. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +44 -16
  287. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +44 -16
  288. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +44 -16
  289. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +44 -16
  290. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +44 -16
  291. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +44 -16
  292. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +44 -16
  293. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +44 -16
  294. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +44 -16
  295. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +44 -16
  296. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +44 -16
  297. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +44 -16
  298. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +44 -16
  299. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +44 -16
  300. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +44 -16
  301. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +44 -16
  302. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +44 -16
  303. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +44 -16
  304. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +44 -16
  305. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +44 -16
  306. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +44 -16
  307. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +44 -16
  308. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +44 -16
  309. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +44 -16
  310. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +44 -16
  311. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +44 -16
  312. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +44 -16
  313. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +44 -16
  314. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +44 -16
  315. nautobot/project-static/docs/user-guide/index.html +44 -16
  316. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +110 -16
  317. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +44 -16
  318. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +44 -16
  319. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +44 -16
  320. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +44 -16
  321. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +47 -19
  322. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +5359 -0
  323. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +47 -19
  324. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +44 -16
  325. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +44 -16
  326. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +44 -16
  327. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +44 -16
  328. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +44 -16
  329. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +44 -16
  330. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +44 -16
  331. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +44 -16
  332. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +44 -16
  333. nautobot/project-static/docs/user-guide/platform-functionality/note.html +44 -16
  334. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +44 -16
  335. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +44 -16
  336. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +113 -44
  337. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +44 -16
  338. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +44 -16
  339. nautobot/project-static/docs/user-guide/platform-functionality/role.html +44 -16
  340. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +44 -16
  341. nautobot/project-static/docs/user-guide/platform-functionality/status.html +44 -16
  342. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +44 -16
  343. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +44 -16
  344. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +44 -16
  345. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +44 -16
  346. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +44 -16
  347. nautobot/project-static/fonts/UFL.txt +96 -0
  348. nautobot/project-static/fonts/Ubuntu-Bold.woff2 +0 -0
  349. nautobot/project-static/fonts/Ubuntu-BoldItalic.woff2 +0 -0
  350. nautobot/project-static/fonts/Ubuntu-Italic.woff2 +0 -0
  351. nautobot/project-static/fonts/Ubuntu-Medium.woff2 +0 -0
  352. nautobot/project-static/fonts/Ubuntu-MediumItalic.woff2 +0 -0
  353. nautobot/project-static/fonts/Ubuntu-Regular.woff2 +0 -0
  354. nautobot/project-static/fonts/UbuntuMono-Bold.woff2 +0 -0
  355. nautobot/project-static/fonts/UbuntuMono-BoldItalic.woff2 +0 -0
  356. nautobot/project-static/fonts/UbuntuMono-Italic.woff2 +0 -0
  357. nautobot/project-static/fonts/UbuntuMono-Regular.woff2 +0 -0
  358. nautobot/project-static/img/dark-theme.png +0 -0
  359. nautobot/project-static/img/light-theme.png +0 -0
  360. nautobot/project-static/img/nautobot_chevron.svg +5 -0
  361. nautobot/project-static/img/nautobot_chevron_header.svg +5 -0
  362. nautobot/project-static/img/system-theme.png +0 -0
  363. nautobot/tenancy/navigation.py +0 -13
  364. nautobot/ui/package-lock.json +2 -2
  365. nautobot/ui/package.json +1 -1
  366. nautobot/users/admin.py +44 -0
  367. nautobot/users/migrations/0007_alter_objectpermission_object_types.py +33 -0
  368. nautobot/users/models.py +3 -2
  369. nautobot/virtualization/navigation.py +1 -33
  370. nautobot/virtualization/tests/test_api.py +1 -1
  371. nautobot/virtualization/tests/test_filters.py +1 -1
  372. {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/METADATA +1 -1
  373. {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/RECORD +376 -351
  374. {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/LICENSE.txt +0 -0
  375. {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/WHEEL +0 -0
  376. {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,3 @@
1
- from django.test.utils import override_settings
2
-
3
1
  from nautobot.core.testing.integration import SeleniumTestCase
4
2
 
5
3
 
@@ -10,19 +8,19 @@ class NavBarTestCase(SeleniumTestCase):
10
8
  navbar = {
11
9
  "Organization": {
12
10
  "Locations": {
13
- "Location": {
11
+ "Locations": {
14
12
  "permission": "dcim.view_location",
15
- "buttons": ["Add", "Import"],
13
+ "buttons": ["Add"],
16
14
  },
17
15
  "Location Types": {
18
16
  "permission": "dcim.view_locationtype",
19
- "buttons": ["Add", "Import"],
17
+ "buttons": ["Add"],
20
18
  },
21
19
  },
22
20
  "Tags": {
23
21
  "Tags": {
24
22
  "permission": "extras.view_tag",
25
- "buttons": ["Add", "Import"],
23
+ "buttons": ["Add"],
26
24
  },
27
25
  },
28
26
  },
@@ -46,7 +44,6 @@ class NavBarTestCase(SeleniumTestCase):
46
44
  self.logout()
47
45
  super().tearDown()
48
46
 
49
- @override_settings(HIDE_RESTRICTED_UI=False)
50
47
  def test_navbar_render_superuser(self):
51
48
  """
52
49
  Render navbar from home page with superuser.
@@ -60,75 +57,24 @@ class NavBarTestCase(SeleniumTestCase):
60
57
 
61
58
  for tab_name, groups in self.navbar.items():
62
59
  # XPath to find tabs using the tab name
63
- tab_xpath = f"//*[@id='navbar']//*[contains(text(), '{tab_name}')]"
60
+ tab_xpath = f"//*[@id='navbar']//*[normalize-space()='{tab_name}']"
64
61
  tab = self.browser.find_by_xpath(tab_xpath)
65
62
  tab.click()
66
63
  self.assertTrue(bool(tab["aria-expanded"]))
67
64
 
68
65
  for group_name, items in groups.items():
69
66
  # Append onto tab xpath with group name search
70
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), '{group_name}')]")
67
+ group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='{group_name}']")
71
68
 
72
69
  for item_name in items:
73
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), '{item_name}')]]"
70
+ item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='{item_name}']]"
74
71
  group.find_by_xpath(item_xpath)
75
72
 
76
- @override_settings(HIDE_RESTRICTED_UI=False)
77
- def test_navbar_render_limit_permissions(self):
78
- """
79
- Render navbar from home page with limited permissions.
80
- """
81
- self.add_permissions("extras.view_relationship")
82
- user_permissions = self.user.get_all_permissions()
83
-
84
- self.browser.visit(self.live_server_url)
85
-
86
- for tab_name, groups in self.navbar.items():
87
- # XPath to find tabs using the tab name
88
- tab_xpath = f"//*[@id='navbar']//*[contains(text(), '{tab_name}')]"
89
- tab = self.browser.find_by_xpath(tab_xpath)
90
- tab.click()
91
- self.assertTrue(bool(tab["aria-expanded"]))
92
-
93
- for group_name, items in groups.items():
94
- # Append onto tab xpath with group name search
95
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), '{group_name}')]")
96
-
97
- for item_name, item_details in items.items():
98
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), '{item_name}')]]"
99
- item = group.find_by_xpath(item_xpath)
100
- if item_details["permission"] in user_permissions:
101
- self.assertNotEqual(item["class"], "disabled", f"Item `{item_name}` should not be disabled.")
102
- else:
103
- self.assertEqual(item["class"], "disabled", f"Item `{item_name}` should be disabled.")
104
-
105
- @override_settings(HIDE_RESTRICTED_UI=False)
106
- def test_navbar_render_no_permissions(self):
107
- """
108
- Render navbar from home page with no permissions.
109
- """
110
- self.browser.visit(self.live_server_url)
111
-
112
- for tab_name, groups in self.navbar.items():
113
- # XPath to find tabs using the tab name
114
- tab_xpath = f"//*[@id='navbar']//*[contains(text(), '{tab_name}')]"
115
- tab = self.browser.find_by_xpath(tab_xpath)
116
73
  tab.click()
117
- self.assertTrue(bool(tab["aria-expanded"]))
118
-
119
- for group_name, items in groups.items():
120
- # Append onto tab xpath with group name search
121
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), '{group_name}')]")
122
-
123
- for item_name in items:
124
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), '{item_name}')]]"
125
- item = group.find_by_xpath(item_xpath)
126
- self.assertEqual(item["class"], "disabled", f"Item `{item_name}` should be disabled.")
127
74
 
128
- @override_settings(HIDE_RESTRICTED_UI=True)
129
- def test_navbar_render_restricted_ui(self):
75
+ def test_navbar_render_with_limited_permissions(self):
130
76
  """
131
- Render navbar from home page with restricted UI set to True.
77
+ Render navbar from home page with limited permissions.
132
78
  This restricts the user to be able to view ONLY relationships on the navbar.
133
79
  It then checks the UI for these restrictions.
134
80
  """
@@ -146,7 +92,7 @@ class NavBarTestCase(SeleniumTestCase):
146
92
  tab_flag = True
147
93
 
148
94
  # XPath to find tabs using the tab name
149
- tabs = self.browser.find_by_xpath(f"//*[@id='navbar']//*[contains(text(), '{tab_name}')]")
95
+ tabs = self.browser.find_by_xpath(f"//*[@id='navbar']//*[normalize-space()='{tab_name}']")
150
96
  if tab_flag:
151
97
  self.assertEqual(len(tabs), 1)
152
98
  else:
@@ -1,4 +1,3 @@
1
- from django.test.utils import override_settings
2
1
  from django.conf import settings
3
2
  from unittest import skipIf
4
3
 
@@ -89,7 +88,6 @@ class PluginHomeTestCase(SeleniumTestCase):
89
88
  counter_html = int(item_html.find_by_xpath("./../../span")["innerHTML"])
90
89
  self.assertEqual(counter, counter_html)
91
90
 
92
- @override_settings(HIDE_RESTRICTED_UI=False)
93
91
  def test_homepage_render_no_permissions(self):
94
92
  """
95
93
  Render homepage with no permissions.
@@ -101,7 +99,8 @@ class PluginHomeTestCase(SeleniumTestCase):
101
99
  columns_html.first.find_by_xpath(f".//strong[text()='{panel_name}']")
102
100
  for item_name, _ in panel_details.items():
103
101
  item_html = columns_html.first.find_by_xpath(f".//h4[contains(text(), '{item_name}')]")
104
- self.assertTrue("mdi mdi-lock" in item_html.find_by_xpath("./../span")["innerHTML"])
102
+ # Assert Panel items without permissions are not shown in DOM
103
+ self.assertFalse(item_html)
105
104
 
106
105
  def test_examplemodel_custom_panel(self):
107
106
  """
@@ -118,7 +117,6 @@ class PluginHomeTestCase(SeleniumTestCase):
118
117
  for item_name in self.custom_panel_examplemodel["items"]:
119
118
  columns_html.first.find_by_xpath(f".//a[contains(text(), '{item_name}')]")
120
119
 
121
- @override_settings(HIDE_RESTRICTED_UI=False)
122
120
  def test_homepage_render_limit_permissions(self):
123
121
  """
124
122
  Render homepage with limited permissions.
@@ -142,4 +140,5 @@ class PluginHomeTestCase(SeleniumTestCase):
142
140
  self.assertEqual(counter, counter_html)
143
141
  else:
144
142
  item_html = columns_html.first.find_by_xpath(f".//h4[contains(text(), '{item_name}')]")
145
- self.assertTrue("mdi mdi-lock" in item_html.find_by_xpath("./../span")["innerHTML"])
143
+ # Assert Layout items without permission is not visible in DOM
144
+ self.assertFalse(item_html)
@@ -19,7 +19,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
19
19
  "Example Group 1": {
20
20
  "Example Model": {
21
21
  "permission": "example_plugin.view_examplemodel",
22
- "buttons": ["Add", "Import"],
22
+ "buttons": ["Add"],
23
23
  },
24
24
  },
25
25
  },
@@ -27,23 +27,23 @@ class PluginNavBarTestCase(SeleniumTestCase):
27
27
  "Circuits": {
28
28
  "Circuits": {
29
29
  "permission": "circuits.view_circuit",
30
- "buttons": ["Add", "Import"],
30
+ "buttons": ["Add"],
31
31
  },
32
- "Circuit Type": {
32
+ "Circuit Types": {
33
33
  "permission": "circuits.view_circuittype",
34
- "buttons": ["Add", "Import"],
34
+ "buttons": ["Add"],
35
35
  },
36
36
  },
37
37
  "Example Circuit Group": {
38
38
  "Example Model": {
39
39
  "permission": "example_plugin.view_examplemodel",
40
- "buttons": ["Add", "Import"],
40
+ "buttons": ["Add"],
41
41
  },
42
42
  },
43
43
  "Providers": {
44
44
  "Providers": {
45
45
  "permission": "circuits.view_provider",
46
- "buttons": ["Add", "Import"],
46
+ "buttons": ["Add"],
47
47
  },
48
48
  },
49
49
  },
@@ -51,7 +51,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
51
51
  "Example Nautobot App": {
52
52
  "Models": {
53
53
  "permission": "example_plugin.view_examplemodel",
54
- "buttons": ["Add a new example model", "Import example models"],
54
+ "buttons": ["Add a new example model"],
55
55
  },
56
56
  "Other Models": {
57
57
  "permission": "example_plugin.view_examplemodel",
@@ -80,14 +80,14 @@ class PluginNavBarTestCase(SeleniumTestCase):
80
80
  # Retrieve home page
81
81
  self.browser.visit(self.live_server_url)
82
82
 
83
- tab_xpath = "//*[@id='navbar']//*[contains(text(), 'Example Menu')]"
83
+ tab_xpath = "//*[@id='navbar']//*[normalize-space()='Example Menu']"
84
84
  tab = self.browser.find_by_xpath(tab_xpath)
85
85
  tab.click()
86
86
  self.assertTrue(bool(tab["aria-expanded"]))
87
87
 
88
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), 'Example Group 1')]")
88
+ group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='Example Group 1']")
89
89
 
90
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), 'Example Model')]]"
90
+ item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='Example Model']]"
91
91
  group.find_by_xpath(item_xpath)
92
92
 
93
93
  def test_plugin_navbar_modify_circuits(self):
@@ -101,15 +101,15 @@ class PluginNavBarTestCase(SeleniumTestCase):
101
101
  # Retrieve home page
102
102
  self.browser.visit(self.live_server_url)
103
103
 
104
- tab_xpath = "//*[@id='navbar']//*[contains(text(), 'Circuits')]"
104
+ tab_xpath = "//*[@id='navbar']//*[normalize-space()='Circuits']"
105
105
  tab = self.browser.find_by_xpath(tab_xpath)
106
106
  tab.click()
107
107
  self.assertTrue(bool(tab["aria-expanded"]))
108
108
 
109
109
  for group_name, items in self.navbar["Circuits"].items():
110
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), '{group_name}')]")
110
+ group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='{group_name}']")
111
111
  for item_name, item_details in items.items():
112
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), '{item_name}')]]"
112
+ item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='{item_name}']]"
113
113
  item = group.find_by_xpath(item_xpath)
114
114
 
115
115
  for button_name in item_details["buttons"]:
@@ -120,6 +120,8 @@ class PluginNavBarTestCase(SeleniumTestCase):
120
120
  icon = button.find_by_xpath(f"{item_xpath}/div//a[@title='{button_name}']/i")
121
121
  self.assertIn(button_icon, icon["class"])
122
122
 
123
+ tab.click()
124
+
123
125
  def test_plugin_navbar_plugin_tab(self):
124
126
  """
125
127
  Test that old-style plugin menu definitions are correctly rendered to the Plugins menu tab.
@@ -131,15 +133,15 @@ class PluginNavBarTestCase(SeleniumTestCase):
131
133
  # Retrieve home page
132
134
  self.browser.visit(self.live_server_url)
133
135
 
134
- tab_xpath = "//*[@id='navbar']//*[contains(text(), 'Plugins')]"
136
+ tab_xpath = "//*[@id='navbar']//*[normalize-space()='Plugins']"
135
137
  tab = self.browser.find_by_xpath(tab_xpath)
136
138
  tab.click()
137
139
  self.assertTrue(bool(tab["aria-expanded"]))
138
140
 
139
141
  for group_name, items in self.navbar["Plugins"].items():
140
- group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[contains(text(), '{group_name}')]")
142
+ group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='{group_name}']")
141
143
  for item_name, item_details in items.items():
142
- item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[contains(text(), '{item_name}')]]"
144
+ item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='{item_name}']]"
143
145
  item = group.find_by_xpath(item_xpath)
144
146
 
145
147
  for button_name in item_details["buttons"]:
@@ -149,3 +151,5 @@ class PluginNavBarTestCase(SeleniumTestCase):
149
151
  if button_icon := getattr(ButtonActionIconChoices, button_name.upper(), None):
150
152
  icon = button.find_by_xpath(f"{item_xpath}/div//a[@title='{button_name}']/i")
151
153
  self.assertIn(button_icon, icon["class"])
154
+
155
+ tab.click()
@@ -4,6 +4,10 @@ from nautobot.core.testing.integration import SeleniumTestCase
4
4
  class ThemeTestCase(SeleniumTestCase):
5
5
  """Integration test to check theme selection modal functionality."""
6
6
 
7
+ def setUp(self):
8
+ super().setUp()
9
+ self.login(self.user.username, self.password)
10
+
7
11
  def test_modal_not_rendered(self):
8
12
  """As implemented, modal dialog box does not render until activated."""
9
13
 
@@ -1,4 +1,3 @@
1
- from copy import deepcopy
2
1
  import csv
3
2
  from io import BytesIO, StringIO
4
3
  import json
@@ -6,6 +5,7 @@ from unittest import skip
6
5
 
7
6
  from django.contrib.contenttypes.models import ContentType
8
7
  from django.conf import settings
8
+ from django.contrib.auth import get_user_model
9
9
  from django.test import RequestFactory, override_settings, TestCase
10
10
  from django.urls import reverse
11
11
 
@@ -29,6 +29,9 @@ from nautobot.ipam import models as ipam_models
29
29
  from nautobot.ipam.api import serializers as ipam_serializers
30
30
 
31
31
 
32
+ User = get_user_model()
33
+
34
+
32
35
  class AppTest(testing.APITestCase):
33
36
  def test_root(self):
34
37
  url = reverse("api-root")
@@ -73,6 +76,9 @@ class APIDocsTestCase(TestCase):
73
76
  self.cf_text.save()
74
77
 
75
78
  def test_api_docs(self):
79
+ user = User.objects.create_user(username="nautobotuser")
80
+ self.client.force_login(user)
81
+
76
82
  url = reverse("api_docs")
77
83
  response = self.client.get(url)
78
84
  self.assertEqual(response.status_code, 200)
@@ -183,17 +189,6 @@ class APIVersioningTestCase(testing.APITestCase):
183
189
  Testing our custom API versioning, NautobotAPIVersioning.
184
190
  """
185
191
 
186
- OVERRIDE_REST_FRAMEWORK = deepcopy(settings.REST_FRAMEWORK)
187
- # For Nautobot 2.0, the default/only supported API version is 2.0, which limits our ability to test with multiple
188
- # or min/max versions. For test purposes we force there to be some extra versions available.
189
- EXTRA_ALLOWED_VERSIONS = [
190
- f"{settings.VERSION_MAJOR - 1}.99",
191
- *settings.REST_FRAMEWORK["ALLOWED_VERSIONS"],
192
- f"{settings.VERSION_MAJOR}.{settings.VERSION_MINOR + 1}",
193
- ]
194
- OVERRIDE_REST_FRAMEWORK["ALLOWED_VERSIONS"] = EXTRA_ALLOWED_VERSIONS
195
- OVERRIDE_REST_FRAMEWORK["DEFAULT_VERSION"] = EXTRA_ALLOWED_VERSIONS[-1]
196
-
197
192
  def test_default_version(self):
198
193
  """Test that a request with no specific API version gets the default version, which is the current version."""
199
194
  url = reverse("api-root")
@@ -203,13 +198,6 @@ class APIVersioningTestCase(testing.APITestCase):
203
198
  self.assertEqual(response["API-Version"], api_settings.DEFAULT_VERSION)
204
199
  self.assertEqual(response["API-Version"], f"{settings.VERSION_MAJOR}.{settings.VERSION_MINOR}")
205
200
 
206
- with override_settings(REST_FRAMEWORK=self.OVERRIDE_REST_FRAMEWORK):
207
- response = self.client.get(url, **self.header)
208
- self.assertHttpStatus(response, 200)
209
- self.assertIn("API-Version", response)
210
- self.assertEqual(response["API-Version"], self.OVERRIDE_REST_FRAMEWORK["DEFAULT_VERSION"])
211
- self.assertEqual(response["API-Version"], f"{settings.VERSION_MAJOR}.{settings.VERSION_MINOR + 1}")
212
-
213
201
  def test_allowed_versions(self):
214
202
  """Test that all expected versions are supported."""
215
203
  for minor_version in range(0, settings.VERSION_MINOR + 1):
@@ -237,7 +225,7 @@ class APIVersioningTestCase(testing.APITestCase):
237
225
  self.assertEqual(response["API-Version"], default_version)
238
226
 
239
227
  max_version = api_settings.ALLOWED_VERSIONS[-1]
240
- self.set_api_version(min_version)
228
+ self.set_api_version(max_version)
241
229
  response = self.client.get(url, **self.header)
242
230
  self.assertHttpStatus(response, 200)
243
231
  self.assertIn("API-Version", response)
@@ -250,29 +238,6 @@ class APIVersioningTestCase(testing.APITestCase):
250
238
  for version in api_settings.ALLOWED_VERSIONS:
251
239
  self.assertIn(version, response.data["detail"])
252
240
 
253
- # Also test with explicitly added additional allowed versions
254
- with override_settings(REST_FRAMEWORK=self.OVERRIDE_REST_FRAMEWORK):
255
- min_version = self.EXTRA_ALLOWED_VERSIONS[0]
256
- self.set_api_version(min_version)
257
- response = self.client.get(url, **self.header)
258
- self.assertHttpStatus(response, 200)
259
- self.assertIn("API-Version", response)
260
- self.assertEqual(response["API-Version"], min_version)
261
-
262
- max_version = self.EXTRA_ALLOWED_VERSIONS[-1]
263
- self.set_api_version(max_version)
264
- response = self.client.get(url, **self.header)
265
- self.assertHttpStatus(response, 200)
266
- self.assertIn("API-Version", response)
267
- self.assertEqual(response["API-Version"], max_version)
268
-
269
- invalid_version = "0.0"
270
- self.set_api_version(invalid_version)
271
- response = self.client.get(url, **self.header)
272
- self.assertHttpStatus(response, 406) # Not Acceptable
273
- for version in self.EXTRA_ALLOWED_VERSIONS:
274
- self.assertIn(version, response.data["detail"])
275
-
276
241
  def test_query_version(self):
277
242
  """Test that the API version can be specified via a query parameter."""
278
243
  url = reverse("api-root")
@@ -301,33 +266,12 @@ class APIVersioningTestCase(testing.APITestCase):
301
266
  for version in api_settings.ALLOWED_VERSIONS:
302
267
  self.assertIn(version, response.data["detail"])
303
268
 
304
- # Also test with explicitly added additional allowed versions
305
- with override_settings(REST_FRAMEWORK=self.OVERRIDE_REST_FRAMEWORK):
306
- min_version = self.EXTRA_ALLOWED_VERSIONS[0]
307
- response = self.client.get(f"{url}?api_version={min_version}", **self.header)
308
- self.assertHttpStatus(response, 200)
309
- self.assertIn("API-Version", response)
310
- self.assertEqual(response["API-Version"], min_version)
311
-
312
- max_version = self.EXTRA_ALLOWED_VERSIONS[-1]
313
- response = self.client.get(f"{url}?api_version={max_version}", **self.header)
314
- self.assertHttpStatus(response, 200)
315
- self.assertIn("API-Version", response)
316
- self.assertEqual(response["API-Version"], max_version)
317
-
318
- invalid_version = "0.0"
319
- response = self.client.get(f"{url}?api_version={invalid_version}", **self.header)
320
- self.assertHttpStatus(response, 404)
321
- for version in self.EXTRA_ALLOWED_VERSIONS:
322
- self.assertIn(version, response.data["detail"])
323
-
324
- @override_settings(REST_FRAMEWORK=OVERRIDE_REST_FRAMEWORK)
325
269
  def test_header_and_query_version(self):
326
270
  """Test the behavior when the API version is specified in both the Accept header *and* a query parameter."""
327
271
  url = reverse("api-root")
328
272
 
329
- min_version = self.EXTRA_ALLOWED_VERSIONS[0]
330
- max_version = self.EXTRA_ALLOWED_VERSIONS[-1]
273
+ min_version = api_settings.ALLOWED_VERSIONS[0]
274
+ max_version = api_settings.ALLOWED_VERSIONS[-1]
331
275
  # Specify same version both as Accept header and as query parameter (valid)
332
276
  self.set_api_version(max_version)
333
277
  response = self.client.get(f"{url}?api_version={max_version}", **self.header)
@@ -873,6 +817,9 @@ class APIOrderingTestCase(testing.APITestCase):
873
817
  class NewUIGetMenuAPIViewTestCase(testing.APITestCase):
874
818
  def test_get_menu(self):
875
819
  """Asset response from new ui nav menu api returns a well formatted registry["new_ui_nav_menu"] expected by nautobot-ui."""
820
+ self.user.is_superuser = True
821
+ self.user.save()
822
+
876
823
  url = reverse("ui-api:get-menu")
877
824
  response = self.client.get(url, **self.header)
878
825
  expected_response = {
@@ -975,6 +922,7 @@ class NewUIGetMenuAPIViewTestCase(testing.APITestCase):
975
922
  },
976
923
  },
977
924
  }
925
+
978
926
  self.assertEqual(response.status_code, 200)
979
927
  self.assertEqual(response.data, expected_response)
980
928
 
@@ -1,3 +1,5 @@
1
+ import datetime
2
+
1
3
  from django.conf import settings
2
4
  from django.contrib.auth import get_user_model
3
5
  from django.contrib.contenttypes.models import ContentType
@@ -9,6 +11,8 @@ from django.test.utils import isolate_apps
9
11
  import django_filters
10
12
  from tree_queries.models import TreeNodeForeignKey
11
13
 
14
+ from nautobot.circuits.filters import CircuitFilterSet
15
+ from nautobot.circuits.models import Circuit, CircuitType, Provider
12
16
  from nautobot.core import filters, testing
13
17
  from nautobot.core.models import fields as core_fields
14
18
  from nautobot.core.utils import lookup
@@ -1450,6 +1454,129 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
1450
1454
  q = filters.SearchFilter(filter_predicates={"asn": ["icontains"]})
1451
1455
 
1452
1456
 
1457
+ class LookupIsNullTest(TestCase, testing.NautobotTestCaseMixin):
1458
+ """
1459
+ Validate isnull is properly applied and filtering.
1460
+ """
1461
+
1462
+ platform_queryset = dcim_models.Platform.objects.all()
1463
+ platform_filterset = dcim_filters.PlatformFilterSet
1464
+ circuit_queryset = Circuit.objects.all()
1465
+ circuit_filterset = CircuitFilterSet
1466
+ device_queryset = dcim_models.Device.objects.all()
1467
+ device_filterset = dcim_filters.DeviceFilterSet
1468
+
1469
+ @classmethod
1470
+ def setUpTestData(cls):
1471
+ location_type = dcim_models.LocationType.objects.create(name="Location Type 1")
1472
+ location_status = extras_models.Status.objects.get_for_model(dcim_models.Location).first()
1473
+ location = dcim_models.Location.objects.create(
1474
+ name="Location 1",
1475
+ location_type=location_type,
1476
+ status=location_status,
1477
+ )
1478
+ manufacturer = dcim_models.Manufacturer.objects.create(name="Manufacturer 1")
1479
+ dcim_models.Platform.objects.create(name="Platform 1")
1480
+ platform = dcim_models.Platform.objects.create(name="Platform 2", manufacturer=manufacturer)
1481
+
1482
+ device_type = dcim_models.DeviceType.objects.create(
1483
+ manufacturer=manufacturer,
1484
+ model="Model 1",
1485
+ is_full_depth=True,
1486
+ )
1487
+ device_role = extras_models.Role.objects.create(name="Active Test")
1488
+ device_role.content_types.add(ContentType.objects.get_for_model(dcim_models.Device))
1489
+
1490
+ device_status = extras_models.Status.objects.get_for_model(dcim_models.Device).first()
1491
+
1492
+ dcim_models.Device.objects.create(
1493
+ name="Device 1",
1494
+ device_type=device_type,
1495
+ role=device_role,
1496
+ platform=platform,
1497
+ location=location,
1498
+ status=device_status,
1499
+ )
1500
+ dcim_models.Device.objects.create(
1501
+ device_type=device_type,
1502
+ role=device_role,
1503
+ platform=platform,
1504
+ location=location,
1505
+ status=device_status,
1506
+ )
1507
+
1508
+ provider = Provider.objects.create(name="provider 1", asn=1)
1509
+ circuit_type = CircuitType.objects.create(name="Circuit Type 1")
1510
+ circuit_status = extras_models.Status.objects.get_for_model(dcim_models.Location).first()
1511
+
1512
+ Circuit.objects.create(
1513
+ cid="Circuit 1",
1514
+ provider=provider,
1515
+ install_date=datetime.date(2020, 1, 1),
1516
+ commit_rate=1000,
1517
+ circuit_type=circuit_type,
1518
+ status=circuit_status,
1519
+ )
1520
+ Circuit.objects.create(
1521
+ cid="Circuit 2",
1522
+ provider=provider,
1523
+ circuit_type=circuit_type,
1524
+ status=circuit_status,
1525
+ )
1526
+
1527
+ def test_isnull_on_fk(self):
1528
+ """Test that the `isnull` filter is applied for True and False queries on foreign key fields."""
1529
+ params = {"manufacturer__isnull": True}
1530
+ self.assertQuerysetEqualAndNotEmpty(
1531
+ dcim_filters.PlatformFilterSet(params, self.platform_queryset).qs,
1532
+ dcim_models.Platform.objects.filter(manufacturer__isnull=True),
1533
+ )
1534
+ params = {"manufacturer__isnull": False}
1535
+ self.assertQuerysetEqualAndNotEmpty(
1536
+ dcim_filters.PlatformFilterSet(params, self.platform_queryset).qs,
1537
+ dcim_models.Platform.objects.filter(manufacturer__isnull=False),
1538
+ )
1539
+
1540
+ def test_isnull_on_integer(self):
1541
+ """Test that the `isnull` filter is applied for True and False queries on integer fields."""
1542
+ params = {"commit_rate__isnull": True}
1543
+ self.assertQuerysetEqualAndNotEmpty(
1544
+ CircuitFilterSet(params, self.circuit_queryset).qs,
1545
+ Circuit.objects.filter(commit_rate__isnull=True),
1546
+ )
1547
+ params = {"commit_rate__isnull": False}
1548
+ self.assertQuerysetEqualAndNotEmpty(
1549
+ CircuitFilterSet(params, self.circuit_queryset).qs,
1550
+ Circuit.objects.filter(commit_rate__isnull=False),
1551
+ )
1552
+
1553
+ def test_isnull_on_date(self):
1554
+ """Test that the `isnull` filter is applied for True and False queries on datetime fields."""
1555
+ params = {"install_date__isnull": True}
1556
+ self.assertQuerysetEqualAndNotEmpty(
1557
+ CircuitFilterSet(params, self.circuit_queryset).qs,
1558
+ Circuit.objects.filter(install_date__isnull=True),
1559
+ )
1560
+ params = {"install_date__isnull": False}
1561
+ self.assertQuerysetEqualAndNotEmpty(
1562
+ CircuitFilterSet(params, self.circuit_queryset).qs,
1563
+ Circuit.objects.filter(install_date__isnull=False),
1564
+ )
1565
+
1566
+ def test_isnull_on_char(self):
1567
+ """Test that the `isnull` filter is applied for True and False queries on char fields."""
1568
+ params = {"name__isnull": True}
1569
+ self.assertQuerysetEqualAndNotEmpty(
1570
+ dcim_filters.DeviceFilterSet(params, self.device_queryset).qs,
1571
+ dcim_models.Device.objects.filter(name__isnull=True),
1572
+ )
1573
+ params = {"name__isnull": False}
1574
+ self.assertQuerysetEqualAndNotEmpty(
1575
+ dcim_filters.DeviceFilterSet(params, self.device_queryset).qs,
1576
+ dcim_models.Device.objects.filter(name__isnull=False),
1577
+ )
1578
+
1579
+
1453
1580
  class FilterTypeTest(TestCase):
1454
1581
  client_class = testing.NautobotTestClient
1455
1582
 
@@ -687,7 +687,7 @@ class DynamicFilterFormTest(TestCase):
687
687
  ("racks", "Rack (name or ID)"),
688
688
  ("rack_groups", "Rack groups (name or ID)"),
689
689
  ("shipping_address", "Shipping address"),
690
- ("status", "Status"),
690
+ ("status", "Status (name or ID)"),
691
691
  ("vlans", "Tagged VLANs (VID or ID)"),
692
692
  ("tags", "Tags"),
693
693
  ("tenant_id", 'Tenant (ID) (deprecated, use "tenant" filter instead)'),