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
@@ -47,6 +47,8 @@ from nautobot.dcim.models import (
47
47
  DeviceType,
48
48
  FrontPort,
49
49
  Interface,
50
+ InterfaceRedundancyGroup,
51
+ InterfaceRedundancyGroupAssociation,
50
52
  Location,
51
53
  LocationType,
52
54
  PowerFeed,
@@ -70,7 +72,16 @@ from nautobot.extras.models import (
70
72
  )
71
73
  from nautobot.extras.registry import registry
72
74
  from nautobot.ipam.factory import VLANGroupFactory
73
- from nautobot.ipam.models import IPAddress, VLAN, Namespace, Prefix
75
+ from nautobot.ipam.models import (
76
+ IPAddress,
77
+ IPAddressToInterface,
78
+ Namespace,
79
+ Prefix,
80
+ VLAN,
81
+ VRF,
82
+ VRFDeviceAssignment,
83
+ VRFPrefixAssignment,
84
+ )
74
85
  from nautobot.users.models import ObjectPermission, Token
75
86
  from nautobot.tenancy.models import Tenant
76
87
  from nautobot.virtualization.factory import ClusterTypeFactory
@@ -834,9 +845,55 @@ class GraphQLQueryTest(TestCase):
834
845
  device=cls.device1,
835
846
  status=interface_status,
836
847
  )
848
+ cls.namespace = Namespace.objects.first()
849
+ cls.intr_group_status = Status.objects.get_for_model(InterfaceRedundancyGroup).first()
850
+ cls.interface_redundancy_group_1 = InterfaceRedundancyGroup.objects.create(
851
+ name="IRGroup 1",
852
+ status=cls.intr_group_status,
853
+ )
854
+ cls.interface_redundancy_group_2 = InterfaceRedundancyGroup.objects.create(
855
+ name="IRGroup 2",
856
+ status=cls.intr_group_status,
857
+ )
858
+ cls.interface_redundancy_group_3 = InterfaceRedundancyGroup.objects.create(
859
+ name="IRGroup 3",
860
+ status=cls.intr_group_status,
861
+ )
862
+ cls.irg_associations = (
863
+ InterfaceRedundancyGroupAssociation.objects.create(
864
+ interface_redundancy_group=cls.interface_redundancy_group_1,
865
+ interface=cls.interface11,
866
+ priority=123,
867
+ ),
868
+ InterfaceRedundancyGroupAssociation.objects.create(
869
+ interface_redundancy_group=cls.interface_redundancy_group_2,
870
+ interface=cls.interface12,
871
+ priority=456,
872
+ ),
873
+ InterfaceRedundancyGroupAssociation.objects.create(
874
+ interface_redundancy_group=cls.interface_redundancy_group_3,
875
+ interface=cls.interface11,
876
+ priority=789,
877
+ ),
878
+ InterfaceRedundancyGroupAssociation.objects.create(
879
+ interface_redundancy_group=cls.interface_redundancy_group_3,
880
+ interface=cls.interface12,
881
+ priority=789,
882
+ ),
883
+ )
884
+ prefixes = Prefix.objects.all()[:2]
885
+ cls.namespace = prefixes[0].namespace
886
+ vrfs = (
887
+ VRF.objects.create(name="VRF 1", rd="65000:100", namespace=cls.namespace),
888
+ VRF.objects.create(name="VRF 2", rd="65000:200", namespace=cls.namespace),
889
+ )
890
+ prefixes[0].vrfs.add(vrfs[0])
891
+ prefixes[0].vrfs.add(vrfs[1])
892
+ prefixes[1].vrfs.add(vrfs[0])
893
+ prefixes[1].vrfs.add(vrfs[1])
894
+
837
895
  cls.ip_statuses = list(Status.objects.get_for_model(IPAddress))[:2]
838
896
  cls.prefix_statuses = list(Status.objects.get_for_model(Prefix))[:2]
839
- cls.namespace = Namespace.objects.first()
840
897
  cls.prefix1 = Prefix.objects.create(
841
898
  prefix="10.0.1.0/24", namespace=cls.namespace, status=cls.prefix_statuses[0]
842
899
  )
@@ -855,6 +912,10 @@ class GraphQLQueryTest(TestCase):
855
912
  tenant=cls.tenant2,
856
913
  face="rear",
857
914
  )
915
+ cls.device2.vrfs.add(vrfs[0])
916
+ cls.device2.vrfs.add(vrfs[1])
917
+ cls.device2.vrfs.add(vrfs[0])
918
+ cls.device2.vrfs.add(vrfs[1])
858
919
 
859
920
  cls.interface21 = Interface.objects.create(
860
921
  name="Int1",
@@ -1178,6 +1239,108 @@ query {
1178
1239
  # Assert GraphQL returned properties match those expected
1179
1240
  self.assertEqual(console_port_entry["connected_console_server_port"], connected_console_server_port)
1180
1241
 
1242
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1243
+ def test_interface_redundancy_group_associations(self):
1244
+ """Test graphql functionality for InterfaceRedundancyGroupAssociation"""
1245
+
1246
+ query = """\
1247
+ query {
1248
+ interface_redundancy_group_associations {
1249
+ id
1250
+ interface { id }
1251
+ interface_redundancy_group { id }
1252
+ priority
1253
+ }
1254
+ }"""
1255
+
1256
+ result = self.execute_query(query)
1257
+ self.assertIsNone(result.errors)
1258
+ self.assertEqual(
1259
+ len(InterfaceRedundancyGroupAssociation.objects.all()),
1260
+ len(result.data["interface_redundancy_group_associations"]),
1261
+ )
1262
+ for association in result.data["interface_redundancy_group_associations"]:
1263
+ association_obj = InterfaceRedundancyGroupAssociation.objects.get(id=association["id"])
1264
+ # Assert GraphQL returned properties match those expected
1265
+ self.assertEqual(association["interface"]["id"], str(association_obj.interface.pk))
1266
+ self.assertEqual(
1267
+ association["interface_redundancy_group"]["id"], str(association_obj.interface_redundancy_group.pk)
1268
+ )
1269
+ self.assertEqual(association["priority"], association_obj.priority)
1270
+
1271
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1272
+ def test_ip_address_to_interface(self):
1273
+ """Test graphql functionality for IPAddressToInterface"""
1274
+
1275
+ query = """\
1276
+ query {
1277
+ ip_address_assignments {
1278
+ id
1279
+ interface { id }
1280
+ vm_interface { id }
1281
+ ip_address { id }
1282
+ }
1283
+ }"""
1284
+
1285
+ result = self.execute_query(query)
1286
+ self.assertIsNone(result.errors)
1287
+ self.assertEqual(
1288
+ len(IPAddressToInterface.objects.all()),
1289
+ len(result.data["ip_address_assignments"]),
1290
+ )
1291
+ for association in result.data["ip_address_assignments"]:
1292
+ association_obj = IPAddressToInterface.objects.get(id=association["id"])
1293
+ # Assert GraphQL returned properties match those expected
1294
+ if association_obj.interface:
1295
+ self.assertEqual(association["interface"]["id"], str(association_obj.interface.pk))
1296
+ else:
1297
+ self.assertEqual(association["interface"], None)
1298
+ if association_obj.vm_interface:
1299
+ self.assertEqual(association["vm_interface"]["id"], str(association_obj.vm_interface.pk))
1300
+ else:
1301
+ self.assertEqual(association["vm_interface"], None)
1302
+ self.assertEqual(association["ip_address"]["id"], str(association_obj.ip_address.pk))
1303
+
1304
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1305
+ def test_vrf_assignments(self):
1306
+ """Test graphql functionality for VRFDeviceAssignment and VRFPrefixAssignment"""
1307
+
1308
+ query = """\
1309
+ query {
1310
+ vrf_device_assignments {
1311
+ id
1312
+ vrf { id }
1313
+ device { id }
1314
+ }
1315
+ vrf_prefix_assignments {
1316
+ id
1317
+ vrf { id }
1318
+ prefix { id }
1319
+ }
1320
+ }"""
1321
+
1322
+ result = self.execute_query(query)
1323
+ self.assertIsNone(result.errors)
1324
+ self.assertEqual(
1325
+ len(VRFDeviceAssignment.objects.all()),
1326
+ len(result.data["vrf_device_assignments"]),
1327
+ )
1328
+ self.assertEqual(
1329
+ len(VRFPrefixAssignment.objects.all()),
1330
+ len(result.data["vrf_prefix_assignments"]),
1331
+ )
1332
+ for assignment in result.data["vrf_device_assignments"]:
1333
+ assignment_obj = VRFDeviceAssignment.objects.get(id=assignment["id"])
1334
+ # Assert GraphQL returned properties match those expected
1335
+ self.assertEqual(assignment["vrf"]["id"], str(assignment_obj.vrf.pk))
1336
+ self.assertEqual(assignment["device"]["id"], str(assignment_obj.device.pk))
1337
+
1338
+ for assignment in result.data["vrf_prefix_assignments"]:
1339
+ assignment_obj = VRFPrefixAssignment.objects.get(id=assignment["id"])
1340
+ # Assert GraphQL returned properties match those expected
1341
+ self.assertEqual(assignment["vrf"]["id"], str(assignment_obj.vrf.pk))
1342
+ self.assertEqual(assignment["prefix"]["id"], str(assignment_obj.prefix.pk))
1343
+
1181
1344
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1182
1345
  def test_query_console_server_ports_cable_peer(self):
1183
1346
  """Test querying console server port terminations for their cable peers"""
@@ -0,0 +1,112 @@
1
+ from pathlib import Path
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+ import yaml
5
+
6
+ from nautobot.core.testing import TransactionTestCase, create_job_result_and_run_job
7
+ from nautobot.dcim.models import DeviceType, Manufacturer
8
+ from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
9
+ from nautobot.extras.models import ExportTemplate, JobLogEntry, Status
10
+ from nautobot.users.models import ObjectPermission
11
+
12
+
13
+ class ExportObjectListTest(TransactionTestCase):
14
+ """
15
+ Test the ExportObjectList system job.
16
+ """
17
+
18
+ databases = ("default", "job_logs")
19
+
20
+ def test_export_without_permission(self):
21
+ """Job should enforce user permissions on the content-type being asked for export."""
22
+ job_result = create_job_result_and_run_job(
23
+ "nautobot.core.jobs",
24
+ "ExportObjectList",
25
+ username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
26
+ content_type=ContentType.objects.get_for_model(Status).pk,
27
+ )
28
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
29
+ log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
30
+ self.assertEqual(log_error.message, f'User "{self.user}" does not have permission to view status objects')
31
+ self.assertFalse(job_result.files.exists())
32
+
33
+ def test_export_with_constrained_permission(self):
34
+ """Job should only allow the user to export objects they have permission to view."""
35
+ instance1, instance2 = Status.objects.all()[:2]
36
+ obj_perm = ObjectPermission(
37
+ name="Test permission",
38
+ constraints={"pk": instance1.pk},
39
+ actions=["view"],
40
+ )
41
+ obj_perm.save()
42
+ obj_perm.users.add(self.user)
43
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Status))
44
+ job_result = create_job_result_and_run_job(
45
+ "nautobot.core.jobs",
46
+ "ExportObjectList",
47
+ username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
48
+ content_type=ContentType.objects.get_for_model(Status).pk,
49
+ )
50
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
51
+ self.assertTrue(job_result.files.exists())
52
+ self.assertEqual(Path(job_result.files.first().file.name).name, "nautobot_statuses.csv")
53
+ csv_data = job_result.files.first().file.read().decode("utf-8")
54
+ self.assertIn(str(instance1.pk), csv_data)
55
+ self.assertNotIn(str(instance2.pk), csv_data)
56
+
57
+ def test_export_all_to_csv(self):
58
+ """By default, job should export all instances to CSV."""
59
+ job_result = create_job_result_and_run_job(
60
+ "nautobot.core.jobs",
61
+ "ExportObjectList",
62
+ content_type=ContentType.objects.get_for_model(Status).pk,
63
+ )
64
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
65
+ self.assertTrue(job_result.files.exists())
66
+ self.assertEqual(Path(job_result.files.first().file.name).name, "nautobot_statuses.csv")
67
+ csv_data = job_result.files.first().file.read().decode("utf-8")
68
+ # May be more than one line per Status if they have newlines in their description strings
69
+ self.assertGreaterEqual(len(csv_data.split("\n")), Status.objects.count() + 1, csv_data) # +1 for CSV header
70
+
71
+ def test_export_all_via_export_template(self):
72
+ """When an export-template is specified, it should be used."""
73
+ et = ExportTemplate.objects.create(
74
+ content_type=ContentType.objects.get_for_model(Status),
75
+ name="Simple Export Template",
76
+ template_code="{% for obj in queryset %}{{ obj.name }}\n{% endfor %}",
77
+ file_extension="txt",
78
+ )
79
+ job_result = create_job_result_and_run_job(
80
+ "nautobot.core.jobs",
81
+ "ExportObjectList",
82
+ content_type=ContentType.objects.get_for_model(Status).pk,
83
+ export_template=et.pk,
84
+ )
85
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
86
+ self.assertTrue(job_result.files.exists())
87
+ self.assertEqual(Path(job_result.files.first().file.name).name, "nautobot_statuses.txt")
88
+ text_data = job_result.files.first().file.read().decode("utf-8")
89
+ self.assertEqual(len(text_data.split("\n")), Status.objects.count() + 1)
90
+ for status in Status.objects.iterator():
91
+ self.assertIn(status.name, text_data)
92
+
93
+ def test_export_devicetype_to_yaml(self):
94
+ """Export device-type to YAML."""
95
+ mfr = Manufacturer.objects.create(name="Cisco")
96
+ DeviceType.objects.create(
97
+ manufacturer=mfr,
98
+ model="Cisco CSR1000v",
99
+ u_height=0,
100
+ )
101
+ job_result = create_job_result_and_run_job(
102
+ "nautobot.core.jobs",
103
+ "ExportObjectList",
104
+ content_type=ContentType.objects.get_for_model(DeviceType).pk,
105
+ export_format="yaml",
106
+ )
107
+ self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
108
+ self.assertTrue(job_result.files.exists())
109
+ self.assertEqual(Path(job_result.files.first().file.name).name, "nautobot_device_types.yaml")
110
+ yaml_data = job_result.files.first().file.read().decode("utf-8")
111
+ data = yaml.safe_load(yaml_data)
112
+ self.assertEqual(data["manufacturer"], "Cisco")
@@ -41,6 +41,9 @@ class OpenAPITest(TestCase):
41
41
  query_params = self.schema["paths"]["/dcim/devices/"]["get"]["parameters"]
42
42
  at_least_one_test = False
43
43
  for query_param_info in query_params:
44
+ if query_param_info["name"].endswith("_isnull"):
45
+ # The broad catch below does not apply to isnull, which will return a boolean.
46
+ continue
44
47
  if query_param_info["name"].startswith("created") or query_param_info["name"].startswith("last_updated"):
45
48
  self.assertEqual("array", query_param_info["schema"]["type"])
46
49
  self.assertEqual("string", query_param_info["schema"]["items"]["type"])
@@ -57,6 +60,9 @@ class OpenAPITest(TestCase):
57
60
  query_params = self.schema["paths"]["/dcim/devices/"]["get"]["parameters"]
58
61
  at_least_one_test = False
59
62
  for query_param_info in query_params:
63
+ if query_param_info["name"].endswith("_isnull"):
64
+ # The broad catch below does not apply to isnull, which will return a boolean.
65
+ continue
60
66
  if query_param_info["name"].startswith("device_redundancy_group_priority"):
61
67
  self.assertEqual("array", query_param_info["schema"]["type"])
62
68
  self.assertEqual("integer", query_param_info["schema"]["items"]["type"])
@@ -37,7 +37,7 @@ class HomeViewTestCase(TestCase):
37
37
 
38
38
  # Search bar in nav
39
39
  nav_search_bar_pattern = re.compile(
40
- '<nav.*<form action="/search/" method="get" class="navbar-form navbar-right" id="navbar_search" role="search">.*</form>.*</nav>'
40
+ '<nav.*<form action="/search/" method="get" class="navbar-form" id="navbar_search" role="search">.*</form>.*</nav>'
41
41
  )
42
42
  nav_search_bar_result = nav_search_bar_pattern.search(
43
43
  response.content.decode(response.charset).replace("\n", "")
@@ -53,8 +53,7 @@ class HomeViewTestCase(TestCase):
53
53
 
54
54
  return nav_search_bar_result, body_search_bar_result
55
55
 
56
- @override_settings(HIDE_RESTRICTED_UI=True)
57
- def test_search_bar_not_visible_if_user_not_authenticated_and_hide_restricted_ui_True(self):
56
+ def test_search_bar_not_visible_if_user_not_authenticated(self):
58
57
  self.client.logout()
59
58
 
60
59
  nav_search_bar_result, body_search_bar_result = self.make_request()
@@ -62,23 +61,7 @@ class HomeViewTestCase(TestCase):
62
61
  self.assertIsNone(nav_search_bar_result)
63
62
  self.assertIsNone(body_search_bar_result)
64
63
 
65
- @override_settings(HIDE_RESTRICTED_UI=False)
66
- def test_search_bar_visible_if_user_authenticated_and_hide_restricted_ui_True(self):
67
- nav_search_bar_result, body_search_bar_result = self.make_request()
68
-
69
- self.assertIsNotNone(nav_search_bar_result)
70
- self.assertIsNotNone(body_search_bar_result)
71
-
72
- @override_settings(HIDE_RESTRICTED_UI=False)
73
- def test_search_bar_visible_if_hide_restricted_ui_False(self):
74
- # Assert if user is authenticated
75
- nav_search_bar_result, body_search_bar_result = self.make_request()
76
-
77
- self.assertIsNotNone(nav_search_bar_result)
78
- self.assertIsNotNone(body_search_bar_result)
79
-
80
- # Assert if user is logout
81
- self.client.logout()
64
+ def test_search_bar_visible_if_user_authenticated(self):
82
65
  nav_search_bar_result, body_search_bar_result = self.make_request()
83
66
 
84
67
  self.assertIsNotNone(nav_search_bar_result)
@@ -229,27 +212,8 @@ class NavRestrictedUI(TestCase):
229
212
  response = self.client.get(reverse("home"))
230
213
  return response.content.decode(response.charset)
231
214
 
232
- @override_settings(HIDE_RESTRICTED_UI=True)
233
- def test_installed_apps_visible_to_staff_with_hide_restricted_ui_true(self):
234
- """The "Installed Apps" menu item should be available to is_staff user regardless of HIDE_RESTRICTED_UI."""
235
- # Make user admin
236
- self.user.is_staff = True
237
- self.user.save()
238
-
239
- response_content = self.make_request()
240
- self.assertInHTML(
241
- f"""
242
- <a href="{self.url}"
243
- data-item-weight="{self.item_weight}">
244
- Installed Plugins
245
- </a>
246
- """,
247
- response_content,
248
- )
249
-
250
- @override_settings(HIDE_RESTRICTED_UI=False)
251
- def test_installed_apps_visible_to_staff_with_hide_restricted_ui_false(self):
252
- """The "Installed Apps" menu item should be available to is_staff user regardless of HIDE_RESTRICTED_UI."""
215
+ def test_installed_apps_visible_to_staff(self):
216
+ """The "Installed Apps" menu item should be available to is_staff user."""
253
217
  # Make user admin
254
218
  self.user.is_staff = True
255
219
  self.user.save()
@@ -265,29 +229,11 @@ class NavRestrictedUI(TestCase):
265
229
  response_content,
266
230
  )
267
231
 
268
- @override_settings(HIDE_RESTRICTED_UI=True)
269
- def test_installed_apps_not_visible_to_non_staff_user_with_hide_restricted_ui_true(self):
270
- """The "Installed Apps" menu item should be hidden from a non-staff user when HIDE_RESTRICTED_UI=True."""
271
- response_content = self.make_request()
272
-
273
- self.assertNotRegex(response_content, r"Installed\s+Apps")
274
-
275
- @override_settings(HIDE_RESTRICTED_UI=False)
276
- def test_installed_apps_disabled_to_non_staff_user_with_hide_restricted_ui_false(self):
277
- """The "Installed Apps" menu item should be disabled for a non-staff user when HIDE_RESTRICTED_UI=False."""
232
+ def test_installed_apps_not_visible_to_non_staff_user_without_permission(self):
233
+ """The "Installed Apps" menu item should be hidden from a non-staff user without permission."""
278
234
  response_content = self.make_request()
279
235
 
280
- # print(response_content)
281
-
282
- self.assertInHTML(
283
- f"""
284
- <a href="{self.url}"
285
- data-item-weight="{self.item_weight}">
286
- Installed Plugins
287
- </a>
288
- """,
289
- response_content,
290
- )
236
+ self.assertNotRegex(response_content, r"Installed\s+Plugins")
291
237
 
292
238
 
293
239
  class LoginUI(TestCase):
@@ -326,9 +272,9 @@ class LoginUI(TestCase):
326
272
  sso_login_search_result = self.make_request()
327
273
  self.assertIsNotNone(sso_login_search_result)
328
274
 
329
- @override_settings(HIDE_RESTRICTED_UI=True, BANNER_TOP="Hello, Banner Top", BANNER_BOTTOM="Hello, Banner Bottom")
330
- def test_routes_redirect_back_to_login_if_hide_restricted_ui_true(self):
331
- """Assert that api docs and graphql redirects to login page if user is unauthenticated and HIDE_RESTRICTED_UI=True."""
275
+ @override_settings(BANNER_TOP="Hello, Banner Top", BANNER_BOTTOM="Hello, Banner Bottom")
276
+ def test_routes_redirect_back_to_login_unauthenticated(self):
277
+ """Assert that api docs and graphql redirects to login page if user is unauthenticated."""
332
278
  self.client.logout()
333
279
  headers = {"HTTP_ACCEPT": "text/html"}
334
280
  urls = [reverse("api_docs"), reverse("graphql")]
@@ -346,26 +292,6 @@ class LoginUI(TestCase):
346
292
  self.assertNotIn("Hello, Banner Top", response_content)
347
293
  self.assertNotIn("Hello, Banner Bottom", response_content)
348
294
 
349
- @override_settings(HIDE_RESTRICTED_UI=False, BANNER_TOP="Hello, Banner Top", BANNER_BOTTOM="Hello, Banner Bottom")
350
- def test_routes_no_redirect_back_to_login_if_hide_restricted_ui_false(self):
351
- """Assert that api docs and graphql do not redirects to login page if user is unauthenticated and HIDE_RESTRICTED_UI=False."""
352
- self.client.logout()
353
- headers = {"HTTP_ACCEPT": "text/html"}
354
- urls = [reverse("api_docs"), reverse("graphql")]
355
- for url in urls:
356
- response = self.client.get(url, **headers)
357
- self.assertHttpStatus(response, 200)
358
- self.assertEqual(response.request["PATH_INFO"], url)
359
- response_content = response.content.decode(response.charset).replace("\n", "")
360
- # Assert Footer items(`self.footer_elements`), Banner and Banner Top is not hidden
361
- for footer_text in self.footer_elements:
362
- self.assertInHTML(footer_text, response_content)
363
-
364
- # Only API Docs implements BANNERS
365
- if url == urls[0]:
366
- self.assertInHTML("Hello, Banner Top", response_content)
367
- self.assertInHTML("Hello, Banner Bottom", response_content)
368
-
369
295
 
370
296
  class MetricsViewTestCase(TestCase):
371
297
  def query_and_parse_metrics(self):
nautobot/core/urls.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from django.conf import settings
2
2
  from django.conf.urls import include
3
3
  from django.urls import path
4
+ from django.views.generic import TemplateView
4
5
  from django.views.static import serve
5
6
 
6
7
  from nautobot.core.views import CustomGraphQLView, HomeView, StaticMediaFailureView, SearchView, nautobot_metrics_view
@@ -31,7 +32,7 @@ urlpatterns = [
31
32
  path("api/", include("nautobot.core.api.urls")),
32
33
  # GraphQL
33
34
  path("graphql/", CustomGraphQLView.as_view(graphiql=True), name="graphql"),
34
- # Serving static media in Django
35
+ # Serving static media in Django (TODO: should be DEBUG mode only - "This view is NOT hardened for production use")
35
36
  path("media/<path:path>", serve, {"document_root": settings.MEDIA_ROOT}),
36
37
  # Admin
37
38
  path("admin/", admin_site.urls),
@@ -46,6 +47,10 @@ urlpatterns = [
46
47
  path(r"health/", include("health_check.urls")),
47
48
  # FileProxy attachments download/get URLs used in admin views only
48
49
  path("files/", include("db_file_storage.urls")),
50
+ # Templated css file
51
+ path(
52
+ "template.css", TemplateView.as_view(template_name="template.css", content_type="text/css"), name="template_css"
53
+ ),
49
54
  ]
50
55
 
51
56
 
@@ -192,3 +192,31 @@ def get_table_for_model(model):
192
192
  (Union[Table, None]): Either the `Table` class or `None`
193
193
  """
194
194
  return get_related_class_for_model(model, module_name="tables", object_suffix="Table")
195
+
196
+
197
+ def get_created_and_last_updated_usernames_for_model(instance):
198
+ """
199
+ Args:
200
+ instance: A model class instance
201
+
202
+ Returns:
203
+ created_by: Username of the user that created the instance
204
+ last_updated_by: Username of the user that last modified the instance
205
+ """
206
+ from nautobot.extras.choices import ObjectChangeActionChoices
207
+ from nautobot.extras.models import ObjectChange
208
+
209
+ object_change_records = get_changes_for_model(instance)
210
+ created_by = None
211
+ last_updated_by = None
212
+ try:
213
+ created_by_record = object_change_records.get(action=ObjectChangeActionChoices.ACTION_CREATE)
214
+ created_by = created_by_record.user_name
215
+ except ObjectChange.DoesNotExist:
216
+ pass
217
+
218
+ last_updated_by_record = object_change_records.first()
219
+ if last_updated_by_record:
220
+ last_updated_by = last_updated_by_record.user_name
221
+
222
+ return created_by, last_updated_by
@@ -114,13 +114,12 @@ def get_filterable_params_from_filter_params(filter_params, non_filter_params, f
114
114
  Returns:
115
115
  (QueryDict): Filter param querydict with only queryset filterable params
116
116
  """
117
- for non_filter_param in non_filter_params:
118
- filter_params.pop(non_filter_param, None)
119
-
120
117
  # Some FilterSet field only accept single choice not multiple choices
121
118
  # e.g datetime field, bool fields etc.
122
119
  final_filter_params = {}
123
120
  for field in filter_params.keys():
121
+ if field in non_filter_params:
122
+ continue
124
123
  if filter_params.get(field):
125
124
  # `is_single_choice_field` implements `get_filterset_field`, which throws an exception if a field is not found.
126
125
  # If an exception is thrown, instead of throwing an exception, set `_is_single_choice_field` to 'False'
@@ -25,7 +25,6 @@ from prometheus_client.registry import Collector
25
25
  from nautobot.core.constants import SEARCH_MAX_RESULTS
26
26
  from nautobot.core.forms import SearchForm
27
27
  from nautobot.core.releases import get_latest_release
28
- from nautobot.core.utils.config import get_settings_or_config
29
28
  from nautobot.core.utils.lookup import get_route_for_model
30
29
  from nautobot.extras.models import GraphQLQuery
31
30
  from nautobot.extras.registry import registry
@@ -58,8 +57,8 @@ class HomeView(AccessMixin, TemplateView):
58
57
  return template.render(additional_context)
59
58
 
60
59
  def get(self, request, *args, **kwargs):
61
- # Redirect user to login page if not authenticated and HIDE_RESTRICTED_UI is set to True
62
- if not request.user.is_authenticated and get_settings_or_config("HIDE_RESTRICTED_UI"):
60
+ # Redirect user to login page if not authenticated
61
+ if not request.user.is_authenticated:
63
62
  return self.handle_no_permission()
64
63
  # Check whether a new release is available. (Only for staff/superusers.)
65
64
  new_release = None
@@ -242,7 +241,7 @@ def csrf_failure(request, reason="", template_name="403_csrf_failure.html"):
242
241
 
243
242
  class CustomGraphQLView(GraphQLView):
244
243
  def render_graphiql(self, request, **data):
245
- if not request.user.is_authenticated and get_settings_or_config("HIDE_RESTRICTED_UI"):
244
+ if not request.user.is_authenticated:
246
245
  graphql_url = reverse("graphql")
247
246
  login_url = reverse(settings.LOGIN_URL)
248
247
  return redirect(f"{login_url}?next={graphql_url}")
@@ -39,6 +39,7 @@ from nautobot.core.forms import (
39
39
  from nautobot.core.forms.forms import DynamicFilterFormSet
40
40
  from nautobot.core.templatetags.helpers import bettertitle, validated_viewname
41
41
  from nautobot.core.utils.config import get_settings_or_config
42
+ from nautobot.core.utils.lookup import get_created_and_last_updated_usernames_for_model
42
43
  from nautobot.core.utils.permissions import get_permission_for_model
43
44
  from nautobot.core.utils.requests import (
44
45
  convert_querydict_to_factory_formset_acceptable_querydict,
@@ -100,6 +101,8 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
100
101
  Generic GET handler for accessing an object.
101
102
  """
102
103
  instance = get_object_or_404(self.queryset, **kwargs)
104
+ # Get the ObjectChange records to populate the advanced tab information
105
+ created_by, last_updated_by = get_created_and_last_updated_usernames_for_model(instance)
103
106
 
104
107
  # TODO: this feels inelegant - should the tabs lookup be a dedicated endpoint rather than piggybacking
105
108
  # on the object-retrieve endpoint?
@@ -127,6 +130,8 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
127
130
  "object": instance,
128
131
  "verbose_name": self.queryset.model._meta.verbose_name,
129
132
  "verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
133
+ "created_by": created_by,
134
+ "last_updated_by": last_updated_by,
130
135
  **self.get_extra_context(request, instance),
131
136
  },
132
137
  )
@@ -152,7 +157,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
152
157
  template_name = "generic/object_list.html"
153
158
  action_buttons = ("add", "import", "export")
154
159
  non_filter_params = (
155
- "export", # trigger for CSV/export-template/YAML export
160
+ "export", # trigger for CSV/export-template/YAML export # 3.0 TODO: remove, irrelevant after #4746
156
161
  "page", # used by django-tables2.RequestConfig
157
162
  "per_page", # used by get_paginate_count
158
163
  "sort", # table sorting
@@ -166,7 +171,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
166
171
  def get_required_permission(self):
167
172
  return get_permission_for_model(self.queryset.model, "view")
168
173
 
169
- # TODO: remove this as well?
174
+ # 3.0 TODO: remove, irrelevant after #4746
170
175
  def queryset_to_yaml(self):
171
176
  """
172
177
  Export the queryset of objects as concatenated YAML documents.
@@ -229,7 +234,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
229
234
  filter_form = self.filterset_form(filter_params, label_suffix="")
230
235
 
231
236
  # Check for export template rendering
232
- if request.GET.get("export"):
237
+ if request.GET.get("export"): # 3.0 TODO: remove, irrelevant after #4746
233
238
  et = get_object_or_404(
234
239
  ExportTemplate,
235
240
  content_type=content_type,
@@ -244,7 +249,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
244
249
  )
245
250
 
246
251
  # Check for YAML export support
247
- elif "export" in request.GET and hasattr(model, "to_yaml"):
252
+ elif "export" in request.GET and hasattr(model, "to_yaml"): # 3.0 TODO: remove, irrelevant after #4746
248
253
  response = HttpResponse(self.queryset_to_yaml(), content_type="text/yaml")
249
254
  filename = f"{settings.BRANDING_PREPENDED_FILENAME}{self.queryset.model._meta.verbose_name_plural}.yaml"
250
255
  response["Content-Disposition"] = f'attachment; filename="{filename}"'