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.
- nautobot/circuits/navigation.py +0 -25
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -9
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/circuits/tests/test_filters.py +1 -0
- nautobot/core/api/serializers.py +15 -5
- nautobot/core/api/views.py +18 -19
- nautobot/core/choices.py +1 -1
- nautobot/core/filters.py +12 -4
- nautobot/core/jobs/__init__.py +125 -3
- nautobot/core/management/commands/generate_test_data.py +4 -1
- nautobot/core/models/fields.py +12 -2
- nautobot/core/settings.py +8 -7
- nautobot/core/templates/base_django.html +2 -2
- nautobot/core/templates/buttons/export.html +57 -30
- nautobot/core/templates/generic/object_list.html +2 -2
- nautobot/core/templates/generic/object_retrieve.html +8 -1
- nautobot/core/templates/home.html +5 -5
- nautobot/core/templates/inc/created_updated.html +2 -2
- nautobot/core/templates/inc/footer.html +2 -2
- nautobot/core/templates/inc/javascript.html +0 -10
- nautobot/core/templates/inc/media.html +2 -0
- nautobot/core/templates/inc/nav_menu.html +66 -68
- nautobot/core/templates/inc/object_details_advanced_panel.html +19 -0
- nautobot/core/templates/nautobot_config.py.j2 +10 -4
- nautobot/core/templates/panel_table.html +1 -1
- nautobot/core/templates/template.css +89 -0
- nautobot/core/templates/utilities/templatetags/table_config_form.html +1 -0
- nautobot/core/templatetags/buttons.py +7 -2
- nautobot/core/testing/views.py +34 -4
- nautobot/core/tests/integration/test_home.py +1 -43
- nautobot/core/tests/integration/test_navbar.py +10 -64
- nautobot/core/tests/integration/test_plugin_home.py +4 -5
- nautobot/core/tests/integration/test_plugin_navbar.py +20 -16
- nautobot/core/tests/integration/test_theme.py +4 -0
- nautobot/core/tests/test_api.py +14 -66
- nautobot/core/tests/test_filters.py +127 -0
- nautobot/core/tests/test_forms.py +1 -1
- nautobot/core/tests/test_graphql.py +165 -2
- nautobot/core/tests/test_jobs.py +112 -0
- nautobot/core/tests/test_openapi.py +6 -0
- nautobot/core/tests/test_views.py +11 -85
- nautobot/core/urls.py +6 -1
- nautobot/core/utils/lookup.py +28 -0
- nautobot/core/utils/requests.py +2 -3
- nautobot/core/views/__init__.py +3 -4
- nautobot/core/views/generic.py +9 -4
- nautobot/core/views/mixins.py +4 -2
- nautobot/core/views/renderers.py +5 -0
- nautobot/dcim/models/device_components.py +1 -0
- nautobot/dcim/navigation.py +10 -165
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/tests/features/locations.feature +143 -0
- nautobot/dcim/tests/test_api.py +1 -1
- nautobot/dcim/tests/test_filters.py +11 -3
- nautobot/extras/admin.py +1 -1
- nautobot/extras/api/serializers.py +33 -0
- nautobot/extras/api/urls.py +6 -0
- nautobot/extras/api/views.py +45 -6
- nautobot/extras/factory.py +28 -2
- nautobot/extras/filters/__init__.py +52 -0
- nautobot/extras/filters/mixins.py +4 -29
- nautobot/extras/forms/forms.py +43 -0
- nautobot/extras/jobs.py +31 -9
- nautobot/extras/migrations/0100_fileproxy_job_result.py +32 -0
- nautobot/extras/migrations/0101_externalintegration.py +61 -0
- nautobot/extras/migrations/0102_set_null_objectchange_contenttype.py +32 -0
- nautobot/extras/models/__init__.py +2 -0
- nautobot/extras/models/change_logging.py +2 -2
- nautobot/extras/models/models.py +96 -16
- nautobot/extras/navigation.py +17 -29
- nautobot/extras/signals.py +15 -0
- nautobot/extras/tables.py +27 -0
- nautobot/extras/templates/extras/externalintegration_retrieve.html +37 -0
- nautobot/extras/templates/extras/inc/jobresult.html +24 -0
- nautobot/extras/templates/extras/jobresult.html +24 -0
- nautobot/extras/test_jobs/file_output.py +16 -0
- nautobot/extras/tests/test_api.py +92 -0
- nautobot/extras/tests/test_filters.py +64 -2
- nautobot/extras/tests/test_jobs.py +39 -0
- nautobot/extras/tests/test_models.py +34 -0
- nautobot/extras/tests/test_views.py +22 -2
- nautobot/extras/urls.py +1 -0
- nautobot/extras/views.py +15 -0
- nautobot/ipam/forms.py +16 -0
- nautobot/ipam/models.py +3 -0
- nautobot/ipam/navigation.py +2 -59
- nautobot/ipam/templates/ipam/ipaddress.html +0 -9
- nautobot/ipam/templates/ipam/prefix.html +0 -9
- nautobot/ipam/tests/features/prefixes.feature +134 -0
- nautobot/ipam/tests/test_filters.py +5 -10
- nautobot/ipam/tests/test_views.py +8 -1
- nautobot/ipam/views.py +3 -0
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +191 -191
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +874 -881
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
- nautobot/project-static/css/base.css +135 -99
- nautobot/project-static/css/dark.css +65 -6
- nautobot/project-static/docs/404.html +44 -16
- nautobot/project-static/docs/apps/index.html +44 -16
- nautobot/project-static/docs/apps/nautobot-apps.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1597 -1457
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +45 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +353 -432
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +66 -38
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2154 -2307
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +807 -691
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +58 -30
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +3600 -3456
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +44 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +101 -75
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +3083 -3019
- nautobot/project-static/docs/development/apps/api/configuration-view.html +44 -16
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +44 -16
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +44 -16
- nautobot/project-static/docs/development/apps/api/models/global-search.html +44 -16
- nautobot/project-static/docs/development/apps/api/models/graphql.html +44 -16
- nautobot/project-static/docs/development/apps/api/models/index.html +44 -16
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +44 -16
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +44 -16
- nautobot/project-static/docs/development/apps/api/prometheus.html +44 -16
- nautobot/project-static/docs/development/apps/api/setup.html +44 -16
- nautobot/project-static/docs/development/apps/api/testing.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-detail-views.html +44 -16
- nautobot/project-static/docs/development/apps/api/ui-extensions/tabs.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/base-template.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/index.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/notes.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/urls.html +44 -16
- nautobot/project-static/docs/development/apps/api/views/view-overrides.html +44 -16
- nautobot/project-static/docs/development/apps/index.html +44 -16
- nautobot/project-static/docs/development/apps/migration/code-updates.html +44 -16
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +44 -16
- nautobot/project-static/docs/development/apps/migration/from-v1.html +44 -16
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +44 -16
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +44 -16
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +44 -16
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +44 -16
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +44 -16
- nautobot/project-static/docs/development/core/application-registry.html +44 -16
- nautobot/project-static/docs/development/core/best-practices.html +44 -16
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +44 -16
- nautobot/project-static/docs/development/core/extending-models.html +44 -16
- nautobot/project-static/docs/development/core/generic-views.html +44 -16
- nautobot/project-static/docs/development/core/getting-started.html +44 -16
- nautobot/project-static/docs/development/core/homepage.html +44 -16
- nautobot/project-static/docs/development/core/index.html +44 -16
- nautobot/project-static/docs/development/core/model-features.html +44 -16
- nautobot/project-static/docs/development/core/natural-keys.html +44 -16
- nautobot/project-static/docs/development/core/navigation-menu.html +44 -21
- nautobot/project-static/docs/development/core/react-ui.html +44 -16
- nautobot/project-static/docs/development/core/release-checklist.html +44 -16
- nautobot/project-static/docs/development/core/role-internals.html +44 -16
- nautobot/project-static/docs/development/core/style-guide.html +44 -16
- nautobot/project-static/docs/development/core/templates.html +44 -16
- nautobot/project-static/docs/development/core/testing.html +44 -16
- nautobot/project-static/docs/development/core/user-preferences.html +44 -16
- nautobot/project-static/docs/development/index.html +44 -16
- nautobot/project-static/docs/development/jobs/index.html +280 -234
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +44 -16
- nautobot/project-static/docs/index.html +44 -16
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/index.html +47 -19
- nautobot/project-static/docs/release-notes/version-1.0.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.1.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.2.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.3.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.4.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.5.html +44 -16
- nautobot/project-static/docs/release-notes/version-1.6.html +44 -16
- nautobot/project-static/docs/release-notes/version-2.0.html +47 -19
- nautobot/project-static/docs/release-notes/version-2.1.html +5724 -0
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +247 -237
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +44 -16
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +44 -16
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +44 -16
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +44 -16
- nautobot/project-static/docs/user-guide/administration/configuration/node-configuration.html +44 -16
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +109 -43
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +44 -16
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -19
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/index.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +44 -16
- nautobot/project-static/docs/user-guide/administration/installation/services.html +44 -16
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +44 -16
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +44 -16
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +44 -16
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +44 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +44 -16
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +44 -16
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +44 -16
- nautobot/project-static/docs/user-guide/index.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +110 -16
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +47 -19
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +5359 -0
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +47 -19
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +113 -44
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +44 -16
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +44 -16
- nautobot/project-static/fonts/UFL.txt +96 -0
- nautobot/project-static/fonts/Ubuntu-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Ubuntu-BoldItalic.woff2 +0 -0
- nautobot/project-static/fonts/Ubuntu-Italic.woff2 +0 -0
- nautobot/project-static/fonts/Ubuntu-Medium.woff2 +0 -0
- nautobot/project-static/fonts/Ubuntu-MediumItalic.woff2 +0 -0
- nautobot/project-static/fonts/Ubuntu-Regular.woff2 +0 -0
- nautobot/project-static/fonts/UbuntuMono-Bold.woff2 +0 -0
- nautobot/project-static/fonts/UbuntuMono-BoldItalic.woff2 +0 -0
- nautobot/project-static/fonts/UbuntuMono-Italic.woff2 +0 -0
- nautobot/project-static/fonts/UbuntuMono-Regular.woff2 +0 -0
- nautobot/project-static/img/dark-theme.png +0 -0
- nautobot/project-static/img/light-theme.png +0 -0
- nautobot/project-static/img/nautobot_chevron.svg +5 -0
- nautobot/project-static/img/nautobot_chevron_header.svg +5 -0
- nautobot/project-static/img/system-theme.png +0 -0
- nautobot/tenancy/navigation.py +0 -13
- nautobot/ui/package-lock.json +2 -2
- nautobot/ui/package.json +1 -1
- nautobot/users/admin.py +44 -0
- nautobot/users/migrations/0007_alter_objectpermission_object_types.py +33 -0
- nautobot/users/models.py +3 -2
- nautobot/virtualization/navigation.py +1 -33
- nautobot/virtualization/tests/test_api.py +1 -1
- nautobot/virtualization/tests/test_filters.py +1 -1
- {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/METADATA +1 -1
- {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/RECORD +376 -351
- {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/WHEEL +0 -0
- {nautobot-2.0.5.dist-info → nautobot-2.1.0b1.dist-info}/entry_points.txt +0 -0
nautobot/extras/models/models.py
CHANGED
|
@@ -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.
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
|
487
|
-
`
|
|
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
|
-
|
|
490
|
-
should never use bulk delete operations on `FileProxy` objects,
|
|
491
|
-
are also bulk-deleted,
|
|
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
|
-
|
|
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
|
-
|
|
601
|
+
if get_storage_class(settings.JOB_FILE_IO_STORAGE) == DatabaseFileStorage:
|
|
602
|
+
delete_file(self, "file")
|
|
523
603
|
|
|
524
604
|
|
|
525
605
|
#
|
nautobot/extras/navigation.py
CHANGED
|
@@ -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",
|
nautobot/extras/signals.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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),
|