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,13 +1,15 @@
1
- import json
2
1
  from collections import OrderedDict
2
+ import json
3
3
 
4
4
  from db_file_storage.model_utils import delete_file, delete_file_if_needed
5
5
  from db_file_storage.storage import DatabaseFileStorage
6
6
  from django.conf import settings
7
7
  from django.contrib.contenttypes.fields import GenericForeignKey
8
8
  from django.contrib.contenttypes.models import ContentType
9
- from django.core.serializers.json import DjangoJSONEncoder
9
+ from django.core.files.storage import get_storage_class
10
10
  from django.core.exceptions import ValidationError
11
+ from django.core.serializers.json import DjangoJSONEncoder
12
+ from django.core.validators import MinValueValidator
11
13
  from django.db import models
12
14
  from django.http import HttpResponse
13
15
  from graphene_django.settings import graphene_settings
@@ -20,7 +22,8 @@ from rest_framework.utils.encoders import JSONEncoder
20
22
 
21
23
  from nautobot.core.models import BaseManager, BaseModel
22
24
  from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName
23
- from nautobot.core.models.generics import OrganizationalModel
25
+ from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
26
+ from nautobot.core.models.validators import EnhancedURLValidator
24
27
  from nautobot.core.utils.data import deepmerge, render_jinja2
25
28
  from nautobot.extras.choices import (
26
29
  ButtonClassChoices,
@@ -450,6 +453,50 @@ class ExportTemplate(BaseModel, ChangeLoggedModel, RelationshipModel, NotesMixin
450
453
  self.file_extension = self.file_extension[1:]
451
454
 
452
455
 
456
+ #
457
+ # External integrations
458
+ #
459
+
460
+
461
+ class ExternalIntegration(PrimaryModel):
462
+ """Model for tracking integrations with external applications."""
463
+
464
+ name = models.CharField(max_length=255, unique=True)
465
+ remote_url = models.CharField(
466
+ max_length=500,
467
+ verbose_name="Remote URL",
468
+ validators=[EnhancedURLValidator()],
469
+ )
470
+ secrets_group = models.ForeignKey(
471
+ null=True,
472
+ blank=True,
473
+ to="extras.SecretsGroup",
474
+ on_delete=models.PROTECT,
475
+ help_text="Credentials used for authenticating with the remote system",
476
+ )
477
+ verify_ssl = models.BooleanField(
478
+ default=True,
479
+ verbose_name="Verify SSL",
480
+ help_text="Verify SSL certificates when connecting to the remote system",
481
+ )
482
+ timeout = models.IntegerField(
483
+ default=30,
484
+ validators=[MinValueValidator(0)],
485
+ help_text="Number of seconds to wait for a response",
486
+ )
487
+ extra_config = models.JSONField(
488
+ blank=True,
489
+ null=True,
490
+ help_text="Optional user-defined JSON data for this integration",
491
+ )
492
+
493
+ def __str__(self):
494
+ return f"{self.name} ({self.remote_url})"
495
+
496
+ class Meta:
497
+ ordering = ["name"]
498
+
499
+
453
500
  #
454
501
  # File attachments
455
502
  #
@@ -476,29 +523,55 @@ class FileAttachment(BaseModel):
476
523
 
477
524
 
478
525
  def database_storage():
479
- """Returns storage backend used by `FileProxy.file` to store files in the database."""
526
+ """
527
+ Returns an instance of DatabaseFileStorage() unconditionally.
528
+
529
+ This is kept around to support legacy migrations; it shouldn't generally be used outside that.
530
+ Use `_job_storage()` instead.
531
+ """
480
532
  return DatabaseFileStorage()
481
533
 
482
534
 
535
+ def _job_storage():
536
+ return get_storage_class(settings.JOB_FILE_IO_STORAGE)()
537
+
538
+
539
+ def _upload_to(instance, filename):
540
+ """
541
+ Returns the upload path for attaching the given filename to the given FileProxy instance.
542
+
543
+ Because django-db-file-storage has specific requirements for this path to configure the FileAttachment model,
544
+ this needs to inspect which storage backend is in use in order to make the right determination.
545
+ """
546
+ if get_storage_class(settings.JOB_FILE_IO_STORAGE) == DatabaseFileStorage:
547
+ # must be a string of the form
548
+ # "<app_label>.<ModelName>/<data field name>/<filename field name>/<mimetype field name>/filename"
549
+ return f"extras.FileAttachment/bytes/filename/mimetype/{filename}"
550
+ else:
551
+ return f"files/{instance.pk}-{filename}"
552
+
553
+
483
554
  class FileProxy(BaseModel):
484
- """An object to store a file in the database.
555
+ """A database object to reference and index a file, such as a Job input file or a Job output file.
556
+
557
+ The `file` field can be used like a file handle.
485
558
 
486
- The `file` field can be used like a file handle. The file contents are stored and retrieved from
487
- `FileAttachment` objects.
559
+ The file contents are stored and retrieved from `FileAttachment` objects,
560
+ if `settings.JOB_FILE_IO_STORAGE` is set to use DatabaseFileStorage,
561
+ otherwise they're written to whatever other file storage backend is in use.
488
562
 
489
- The associated `FileAttachment` is removed when `delete()` is called. For this reason, one
490
- should never use bulk delete operations on `FileProxy` objects, unless `FileAttachment` objects
491
- are also bulk-deleted, because a model's `delete()` method is not called during bulk operations.
563
+ When using DatabaseFileStorage, the associated `FileAttachment` is removed when `delete()` is called.
564
+ For this reason, one should never use bulk delete operations on `FileProxy` objects,
565
+ unless `FileAttachment` objects are also bulk-deleted,
566
+ because a model's `delete()` method is not called during bulk operations.
492
567
  In most cases, it is better to iterate over a queryset of `FileProxy` objects and call
493
568
  `delete()` on each one individually.
494
569
  """
495
570
 
496
571
  name = models.CharField(max_length=255)
497
- file = models.FileField(
498
- upload_to="extras.FileAttachment/bytes/filename/mimetype",
499
- storage=database_storage, # Use only this backend
500
- )
572
+ file = models.FileField(upload_to=_upload_to, storage=_job_storage)
501
573
  uploaded_at = models.DateTimeField(auto_now_add=True)
574
+ job_result = models.ForeignKey(to=JobResult, null=True, blank=True, on_delete=models.CASCADE, related_name="files")
502
575
 
503
576
  def __str__(self):
504
577
  return self.name
@@ -514,12 +587,19 @@ class FileProxy(BaseModel):
514
587
  natural_key_field_names = ["name", "uploaded_at"]
515
588
 
516
589
  def save(self, *args, **kwargs):
517
- delete_file_if_needed(self, "file")
590
+ if get_storage_class(settings.JOB_FILE_IO_STORAGE) == DatabaseFileStorage:
591
+ delete_file_if_needed(self, "file")
592
+ else:
593
+ # TODO check whether there's an existing file with a different filename and delete it if so?
594
+ pass
518
595
  super().save(*args, **kwargs)
519
596
 
520
597
  def delete(self, *args, **kwargs):
598
+ if get_storage_class(settings.JOB_FILE_IO_STORAGE) != DatabaseFileStorage:
599
+ self.file.delete()
521
600
  super().delete(*args, **kwargs)
522
- delete_file(self, "file")
601
+ if get_storage_class(settings.JOB_FILE_IO_STORAGE) == DatabaseFileStorage:
602
+ delete_file(self, "file")
523
603
 
524
604
 
525
605
  #
@@ -5,7 +5,6 @@ from nautobot.core.apps import (
5
5
  NavMenuAddButton,
6
6
  NavMenuGroup,
7
7
  NavMenuItem,
8
- NavMenuImportButton,
9
8
  NavMenuTab,
10
9
  )
11
10
 
@@ -33,12 +32,6 @@ menu_items = (
33
32
  "extras.add_tag",
34
33
  ],
35
34
  ),
36
- NavMenuImportButton(
37
- link="extras:tag_import",
38
- permissions=[
39
- "extras.add_tag",
40
- ],
41
- ),
42
35
  ),
43
36
  ),
44
37
  ),
@@ -61,12 +54,6 @@ menu_items = (
61
54
  "extras.add_status",
62
55
  ],
63
56
  ),
64
- NavMenuImportButton(
65
- link="extras:status_import",
66
- permissions=[
67
- "extras.add_status",
68
- ],
69
- ),
70
57
  ),
71
58
  ),
72
59
  ),
@@ -89,12 +76,6 @@ menu_items = (
89
76
  "extras.add_role",
90
77
  ],
91
78
  ),
92
- NavMenuImportButton(
93
- link="extras:role_import",
94
- permissions=[
95
- "extras.add_role",
96
- ],
97
- ),
98
79
  ),
99
80
  ),
100
81
  ),
@@ -136,10 +117,7 @@ menu_items = (
136
117
  name="Secrets",
137
118
  weight=100,
138
119
  permissions=["extras.view_secret"],
139
- buttons=(
140
- NavMenuAddButton(link="extras:secret_add", permissions=["extras.add_secret"]),
141
- NavMenuImportButton(link="extras:secret_import", permissions=["extras.add_secret"]),
142
- ),
120
+ buttons=(NavMenuAddButton(link="extras:secret_add", permissions=["extras.add_secret"]),),
143
121
  ),
144
122
  NavMenuItem(
145
123
  link="extras:secretsgroup_list",
@@ -272,12 +250,6 @@ menu_items = (
272
250
  "extras.add_gitrepository",
273
251
  ],
274
252
  ),
275
- NavMenuImportButton(
276
- link="extras:gitrepository_import",
277
- permissions=[
278
- "extras.add_gitrepository",
279
- ],
280
- ),
281
253
  ),
282
254
  ),
283
255
  ),
@@ -380,6 +352,22 @@ menu_items = (
380
352
  ),
381
353
  ),
382
354
  ),
355
+ NavMenuItem(
356
+ link="extras:externalintegration_list",
357
+ name="External Integrations",
358
+ weight=300,
359
+ permissions=[
360
+ "extras.view_externalintegration",
361
+ ],
362
+ buttons=(
363
+ NavMenuAddButton(
364
+ link="extras:externalintegration_add",
365
+ permissions=[
366
+ "extras.add_externalintegration",
367
+ ],
368
+ ),
369
+ ),
370
+ ),
383
371
  NavMenuItem(
384
372
  link="extras:webhook_list",
385
373
  name="Webhooks",
@@ -5,9 +5,13 @@ import shutil
5
5
  import logging
6
6
  from datetime import timedelta
7
7
 
8
+ from db_file_storage.model_utils import delete_file
9
+ from db_file_storage.storage import DatabaseFileStorage
10
+ from django.conf import settings
8
11
  from django.contrib.contenttypes.models import ContentType
9
12
  from django.core.cache import cache
10
13
  from django.core.exceptions import ValidationError
14
+ from django.core.files.storage import get_storage_class
11
15
  from django.db import transaction
12
16
  from django.db.models.signals import m2m_changed, pre_delete, post_save, pre_save, post_delete
13
17
  from django.dispatch import receiver
@@ -346,6 +350,17 @@ post_save.connect(dynamic_group_update_cached_members, sender=DynamicGroupMember
346
350
  #
347
351
 
348
352
 
353
+ @receiver(pre_delete, sender=JobResult)
354
+ def job_result_delete_associated_files(instance, **kwargs):
355
+ """For each related FileProxy, make sure its file gets deleted correctly from disk or database."""
356
+ if get_storage_class(settings.JOB_FILE_IO_STORAGE) == DatabaseFileStorage:
357
+ for file_proxy in instance.files.all():
358
+ delete_file(file_proxy, "file")
359
+ else:
360
+ for file_proxy in instance.files.all():
361
+ file_proxy.file.delete()
362
+
363
+
349
364
  def refresh_job_models(sender, *, apps, **kwargs):
350
365
  """
351
366
  Callback for the nautobot_database_ready signal; updates Jobs in the database based on Job source file availability.
nautobot/extras/tables.py CHANGED
@@ -26,6 +26,7 @@ from .models import (
26
26
  DynamicGroup,
27
27
  DynamicGroupMembership,
28
28
  ExportTemplate,
29
+ ExternalIntegration,
29
30
  GitRepository,
30
31
  GraphQLQuery,
31
32
  Job as JobModel,
@@ -407,6 +408,32 @@ class ExportTemplateTable(BaseTable):
407
408
  )
408
409
 
409
410
 
411
+ class ExternalIntegrationTable(BaseTable):
412
+ pk = ToggleColumn()
413
+ name = tables.Column(linkify=True)
414
+ remote_url = tables.Column(linkify=False)
415
+ secrets_group = tables.Column(linkify=True)
416
+
417
+ class Meta(BaseTable.Meta):
418
+ model = ExternalIntegration
419
+ fields = (
420
+ "pk",
421
+ "name",
422
+ "remote_url",
423
+ "secrets_group",
424
+ "verify_ssl",
425
+ "timeout",
426
+ )
427
+ default_columns = (
428
+ "pk",
429
+ "name",
430
+ "remote_url",
431
+ "secrets_group",
432
+ "verify_ssl",
433
+ "timeout",
434
+ )
435
+
436
+
410
437
  class GitRepositoryTable(BaseTable):
411
438
  pk = ToggleColumn()
412
439
  name = tables.LinkColumn()
@@ -0,0 +1,37 @@
1
+ {% extends 'generic/object_retrieve.html' %}
2
+ {% load helpers %}
3
+
4
+ {% block content_left_page %}
5
+ <div class="panel panel-default">
6
+ <div class="panel-heading">
7
+ <strong>External Integration</strong>
8
+ </div>
9
+
10
+ <table class="table table-hover panel-body attr-table">
11
+ <tr>
12
+ <td>Name</td>
13
+ <td>{{ object.name }}</td>
14
+ </tr>
15
+ <tr>
16
+ <td>Remote URL</td>
17
+ <td>{{ object.remote_url }}</td>
18
+ </tr>
19
+ <tr>
20
+ <td>Verify SSL</td>
21
+ <td>{{ object.verify_ssl | render_boolean }}</td>
22
+ </tr>
23
+ <tr>
24
+ <td>Secrets Group</td>
25
+ <td>{{ object.secrets_group | hyperlinked_object }}</td>
26
+ </tr>
27
+ <tr>
28
+ <td>Timeout</td>
29
+ <td>{{ object.timeout }}</td>
30
+ </tr>
31
+ <tr>
32
+ <td>Extra Config</td>
33
+ <td><pre>{{ object.extra_config | render_json }}</pre></td>
34
+ </tr>
35
+ </table>
36
+ </div>
37
+ {% endblock content_left_page %}
@@ -50,6 +50,30 @@
50
50
  {% endif %}
51
51
  </td>
52
52
  </tr>
53
+ {% if result.files.exists %}
54
+ <tr>
55
+ <td>File Output(s)</td>
56
+ <td>
57
+ <ul>
58
+ {% for file_proxy in result.files.all %}
59
+ {% if file_proxy.file %}
60
+ <li>
61
+ <a
62
+ {% if settings.JOB_FILE_IO_STORAGE == "db_file_storage.storage.DatabaseFileStorage" %}
63
+ href="{% url "db_file_storage.download_file" %}?name={{ file_proxy.file }}"
64
+ {% else %}
65
+ href="{{ file_proxy.file.url }}"
66
+ {% endif %}
67
+ download="{{ file_proxy.name }}">
68
+ {{ file_proxy.name }}
69
+ </a>
70
+ </li>
71
+ {% endif %}
72
+ {% endfor %}
73
+ </ul>
74
+ </td>
75
+ </tr>
76
+ {% endif %}
53
77
  </table>
54
78
  </div>
55
79
 
@@ -118,6 +118,30 @@
118
118
  {% include 'extras/inc/json_data.html' with data=result.result format="json" %}
119
119
  </div>
120
120
  </div>
121
+ <div class="panel panel-default">
122
+ <div class="panel-heading">
123
+ <strong>File Output(s)</strong>
124
+ </div>
125
+ <div class="panel-body">
126
+ <ul>
127
+ {% for file_proxy in result.files.all %}
128
+ {% if file_proxy.file %}
129
+ <li>
130
+ <a
131
+ {% if settings.JOB_FILE_IO_STORAGE == "db_file_storage.storage.DatabaseFileStorage" %}
132
+ href="{% url "db_file_storage.download_file" %}?name={{ file_proxy.file }}"
133
+ {% else %}
134
+ href="{{ file_proxy.file.url }}"
135
+ {% endif %}
136
+ download="{{ file_proxy.name }}">
137
+ {{ file_proxy.name }}
138
+ </a>
139
+ </li>
140
+ {% endif %}
141
+ {% endfor %}
142
+ </ul>
143
+ </div>
144
+ </div>
121
145
  <div class="panel panel-default">
122
146
  <div class="panel-heading">
123
147
  <strong>Traceback</strong>
@@ -0,0 +1,16 @@
1
+ from nautobot.core.celery import register_jobs
2
+ from nautobot.extras.jobs import IntegerVar, Job
3
+
4
+
5
+ class FileOutputJob(Job):
6
+ lines = IntegerVar()
7
+
8
+ class Meta:
9
+ name = "File Output job"
10
+ description = "Creates a text file as output."
11
+
12
+ def run(self, lines):
13
+ self.create_file("output.txt", "Hello World!\n" * lines)
14
+
15
+
16
+ register_jobs(FileOutputJob)
@@ -48,6 +48,8 @@ from nautobot.extras.models import (
48
48
  DynamicGroup,
49
49
  DynamicGroupMembership,
50
50
  ExportTemplate,
51
+ ExternalIntegration,
52
+ FileProxy,
51
53
  GitRepository,
52
54
  GraphQLQuery,
53
55
  ImageAttachment,
@@ -813,6 +815,96 @@ class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
813
815
  )
814
816
 
815
817
 
818
+ class ExternalIntegrationTest(APIViewTestCases.APIViewTestCase):
819
+ model = ExternalIntegration
820
+ create_data = [
821
+ {
822
+ "name": "Test External Integration 1",
823
+ "remote_url": "ssh://example.com/test1/",
824
+ "verify_ssl": False,
825
+ "timeout": 5,
826
+ "extra_config": "{'foo': 'bar'}",
827
+ },
828
+ {
829
+ "name": "Test External Integration 2",
830
+ "remote_url": "http://example.com/test2/",
831
+ },
832
+ {
833
+ "name": "Test External Integration 3",
834
+ "remote_url": "https://example.com/test3/",
835
+ "verify_ssl": True,
836
+ "timeout": 30,
837
+ "extra_config": "{'foo': ['bat', 'baz']}",
838
+ },
839
+ ]
840
+ bulk_update_data = {"timeout": 10, "verify_ssl": True, "extra_config": r"{}"}
841
+
842
+
843
+ class FileProxyTest(
844
+ APIViewTestCases.GetObjectViewTestCase,
845
+ APIViewTestCases.ListObjectsViewTestCase,
846
+ ):
847
+ model = FileProxy
848
+
849
+ @classmethod
850
+ def setUpTestData(cls):
851
+ job = Job.objects.first()
852
+ job_results = (
853
+ JobResult.objects.create(
854
+ job_model=job,
855
+ name=job.class_path,
856
+ date_done=now(),
857
+ status=JobResultStatusChoices.STATUS_SUCCESS,
858
+ ),
859
+ JobResult.objects.create(
860
+ job_model=job,
861
+ name=job.class_path,
862
+ date_done=now(),
863
+ status=JobResultStatusChoices.STATUS_SUCCESS,
864
+ ),
865
+ JobResult.objects.create(
866
+ job_model=job,
867
+ name=job.class_path,
868
+ date_done=now(),
869
+ status=JobResultStatusChoices.STATUS_SUCCESS,
870
+ ),
871
+ )
872
+ cls.file_proxies = []
873
+ for i, job_result in enumerate(job_results):
874
+ file = SimpleUploadedFile(name=f"Output {i}.txt", content=f"Content {i}\n".encode("utf-8"))
875
+ file_proxy = FileProxy.objects.create(name=file.name, file=file, job_result=job_result)
876
+ cls.file_proxies.append(file_proxy)
877
+
878
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
879
+ def test_download_file_without_permission(self):
880
+ """Test `download` action without permission."""
881
+ url = reverse("extras-api:fileproxy-download", kwargs={"pk": self.file_proxies[0].pk})
882
+ response = self.client.get(url, **self.header)
883
+ self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
884
+
885
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
886
+ def test_download_file_with_permission(self):
887
+ """Test `download` action with permission."""
888
+ obj_perm = ObjectPermission(
889
+ name="Test permission", constraints={"pk": self.file_proxies[0].pk}, actions=["view"]
890
+ )
891
+ obj_perm.validated_save()
892
+ obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
893
+ obj_perm.users.add(self.user)
894
+
895
+ # FileProxy permitted by permission
896
+ url = reverse("extras-api:fileproxy-download", kwargs={"pk": self.file_proxies[0].pk})
897
+ response = self.client.get(url, **self.header)
898
+ self.assertHttpStatus(response, status.HTTP_200_OK)
899
+ content = b"".join(data for data in response)
900
+ self.assertEqual(content.decode("utf-8"), "Content 0\n")
901
+
902
+ # FileProxy not permitted by permission
903
+ url = reverse("extras-api:fileproxy-download", kwargs={"pk": self.file_proxies[1].pk})
904
+ response = self.client.get(url, **self.header)
905
+ self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
906
+
907
+
816
908
  class GitRepositoryTest(APIViewTestCases.APIViewTestCase):
817
909
  model = GitRepository
818
910
  bulk_update_data = {
@@ -2,6 +2,7 @@ import uuid
2
2
 
3
3
  from django.contrib.auth import get_user_model
4
4
  from django.contrib.contenttypes.models import ContentType
5
+ from django.core.files.uploadedfile import SimpleUploadedFile
5
6
  from django.db.models import Q
6
7
  from django.test import override_settings
7
8
 
@@ -33,6 +34,8 @@ from nautobot.extras.filters import (
33
34
  CustomFieldChoiceFilterSet,
34
35
  CustomLinkFilterSet,
35
36
  ExportTemplateFilterSet,
37
+ ExternalIntegrationFilterSet,
38
+ FileProxyFilterSet,
36
39
  GitRepositoryFilterSet,
37
40
  GraphQLQueryFilterSet,
38
41
  ImageAttachmentFilterSet,
@@ -59,6 +62,8 @@ from nautobot.extras.models import (
59
62
  CustomFieldChoice,
60
63
  CustomLink,
61
64
  ExportTemplate,
65
+ ExternalIntegration,
66
+ FileProxy,
62
67
  GitRepository,
63
68
  GraphQLQuery,
64
69
  ImageAttachment,
@@ -500,6 +505,63 @@ class ExportTemplateTestCase(FilterTestCases.FilterTestCase):
500
505
  self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
501
506
 
502
507
 
508
+ class FileProxyTestCase(FilterTestCases.FilterTestCase):
509
+ queryset = FileProxy.objects.all()
510
+ filterset = FileProxyFilterSet
511
+
512
+ generic_filter_tests = (
513
+ ["job", "job_result__job_model__id"],
514
+ ["job", "job_result__job_model__name"],
515
+ ["job_result_id"],
516
+ ["name"],
517
+ ["uploaded_at"],
518
+ )
519
+
520
+ @classmethod
521
+ def setUpTestData(cls):
522
+ jobs = Job.objects.all()[:3]
523
+ job_results = (JobResult.objects.create(job_model=job) for job in jobs)
524
+ for i, job_result in enumerate(job_results):
525
+ FileProxy.objects.create(
526
+ name=f"File {i}.txt", file=SimpleUploadedFile(name=f"File {i}.txt", content=b""), job_result=job_result
527
+ )
528
+
529
+
530
+ class ExternalIntegrationTestCase(FilterTestCases.FilterTestCase):
531
+ queryset = ExternalIntegration.objects.all()
532
+ filterset = ExternalIntegrationFilterSet
533
+
534
+ generic_filter_tests = (
535
+ ["name"],
536
+ ["remote_url"],
537
+ ["timeout"],
538
+ ["secrets_group", "secrets_group__id"],
539
+ ["secrets_group", "secrets_group__name"],
540
+ )
541
+
542
+ @classmethod
543
+ def setUpTestData(cls):
544
+ secrets_groups = (
545
+ SecretsGroup.objects.create(name="Secrets Group 1"),
546
+ SecretsGroup.objects.create(name="Secrets Group 2"),
547
+ )
548
+ external_integrations = list(ExternalIntegration.objects.all()[:2])
549
+ external_integrations[0].secrets_group = secrets_groups[0]
550
+ external_integrations[1].secrets_group = secrets_groups[1]
551
+ for ei in external_integrations:
552
+ ei.validated_save()
553
+
554
+ def test_verify_ssl(self):
555
+ params = {"verify_ssl": True}
556
+ self.assertQuerysetEqualAndNotEmpty(
557
+ self.filterset(params, self.queryset).qs, self.queryset.filter(verify_ssl=True)
558
+ )
559
+ params = {"verify_ssl": False}
560
+ self.assertQuerysetEqualAndNotEmpty(
561
+ self.filterset(params, self.queryset).qs, self.queryset.filter(verify_ssl=False)
562
+ )
563
+
564
+
503
565
  class GitRepositoryTestCase(FilterTestCases.FilterTestCase):
504
566
  queryset = GitRepository.objects.all()
505
567
  filterset = GitRepositoryFilterSet
@@ -1170,7 +1232,7 @@ class RelationshipAssociationTestCase(FilterTestCases.FilterTestCase):
1170
1232
  ),
1171
1233
  )
1172
1234
  vlan_status = Status.objects.get_for_model(VLAN).first()
1173
- vlan_group = VLANGroup.objects.first()
1235
+ vlan_group = VLANGroup.objects.create(name="Test VLANGroup 1")
1174
1236
  cls.vlans = (
1175
1237
  VLAN.objects.create(vid=1, name="VLAN 1", status=vlan_status, vlan_group=vlan_group),
1176
1238
  VLAN.objects.create(vid=2, name="VLAN 2", status=vlan_status, vlan_group=vlan_group),
@@ -1297,7 +1359,7 @@ class RelationshipModelFilterSetTestCase(FilterTestCases.FilterTestCase):
1297
1359
  ),
1298
1360
  )
1299
1361
  vlan_status = Status.objects.get_for_model(VLAN).first()
1300
- vlan_group = VLANGroup.objects.first()
1362
+ vlan_group = VLANGroup.objects.create(name="Test VLANGroup 1")
1301
1363
  cls.vlans = (
1302
1364
  VLAN.objects.create(vid=1, name="VLAN 1", status=vlan_status, vlan_group=vlan_group),
1303
1365
  VLAN.objects.create(vid=2, name="VLAN 2", status=vlan_status, vlan_group=vlan_group),