nautobot 2.4.1__py3-none-any.whl → 2.4.3__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/templates/circuits/inc/circuit_termination.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +135 -0
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/circuits/views.py +4 -1
- nautobot/cloud/api/views.py +3 -3
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/constants.py +0 -1
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +2 -1
- nautobot/core/forms/widgets.py +8 -0
- nautobot/core/management/commands/generate_performance_test_endpoints.py +268 -0
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_create.html +5 -0
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/widgets/clearable_file.html +5 -0
- nautobot/core/templatetags/helpers.py +3 -3
- nautobot/core/testing/integration.py +469 -12
- nautobot/core/tests/test_commands.py +31 -0
- nautobot/core/tests/test_jobs.py +34 -2
- nautobot/core/tests/test_utils.py +17 -2
- nautobot/core/utils/git.py +7 -2
- nautobot/core/utils/lookup.py +12 -1
- nautobot/core/views/generic.py +10 -2
- nautobot/core/views/mixins.py +22 -7
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/api/views.py +11 -10
- nautobot/dcim/forms.py +15 -6
- nautobot/dcim/models/devices.py +1 -2
- nautobot/dcim/tables/devices.py +2 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_trace.html +4 -4
- nautobot/dcim/templates/dcim/consoleport.html +14 -4
- nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/device_component.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/frontport.html +7 -2
- nautobot/dcim/templates/dcim/interface.html +9 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/locationtype.html +1 -1
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -1
- nautobot/dcim/templates/dcim/platform.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +9 -4
- nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +14 -4
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rackgroup.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation.html +2 -2
- nautobot/dcim/templates/dcim/rearport.html +7 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- nautobot/dcim/tests/test_models.py +1 -1
- nautobot/dcim/tests/test_views.py +9 -1
- nautobot/dcim/views.py +12 -15
- nautobot/extras/api/serializers.py +33 -0
- nautobot/extras/api/views.py +13 -5
- nautobot/extras/constants.py +1 -0
- nautobot/extras/datasources/git.py +125 -0
- nautobot/extras/forms/forms.py +4 -0
- nautobot/extras/jobs.py +8 -1
- nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
- nautobot/extras/models/customfields.py +29 -12
- nautobot/extras/models/datasources.py +85 -0
- nautobot/extras/models/models.py +15 -0
- nautobot/extras/models/relationships.py +17 -5
- nautobot/extras/signals.py +15 -1
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/customlink.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/exporttemplate.html +1 -1
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/graphqlquery.html +1 -1
- nautobot/extras/templates/extras/job.html +1 -0
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
- nautobot/extras/templates/extras/jobhook.html +1 -1
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/plugin_detail.html +1 -1
- nautobot/extras/templates/extras/relationship.html +1 -63
- nautobot/extras/templates/extras/role_retrieve.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +1 -1
- nautobot/extras/templates/extras/secret.html +1 -1
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/webhook.html +1 -1
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
- nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
- nautobot/extras/tests/git_helper.py +9 -1
- nautobot/extras/tests/integration/__init__.py +29 -16
- nautobot/extras/tests/test_api.py +6 -0
- nautobot/extras/tests/test_customfields.py +49 -51
- nautobot/extras/tests/test_datasources.py +27 -0
- nautobot/extras/tests/test_dynamicgroups.py +14 -0
- nautobot/extras/tests/test_models.py +283 -0
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/tests/test_views.py +197 -9
- nautobot/extras/utils.py +47 -8
- nautobot/extras/views.py +84 -26
- nautobot/ipam/api/views.py +3 -3
- nautobot/ipam/forms.py +2 -6
- nautobot/ipam/models.py +8 -2
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/templates/ipam/ipaddress.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/rir.html +1 -1
- nautobot/ipam/templates/ipam/routetarget.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +1 -1
- nautobot/ipam/templates/ipam/vrf.html +1 -1
- nautobot/ipam/tests/test_models.py +24 -0
- nautobot/ipam/tests/test_utils.py +41 -2
- nautobot/ipam/utils/__init__.py +18 -11
- nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
- nautobot/project-static/docs/404.html +87 -12
- nautobot/project-static/docs/apps/index.html +88 -13
- nautobot/project-static/docs/apps/nautobot-apps.html +88 -13
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
- nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -21
- nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +89 -14
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
- nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
- nautobot/project-static/docs/development/apps/api/setup.html +88 -13
- nautobot/project-static/docs/development/apps/api/testing.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
- nautobot/project-static/docs/development/apps/index.html +87 -12
- nautobot/project-static/docs/development/apps/migration/code-updates.html +93 -17
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +89 -14
- nautobot/project-static/docs/development/apps/migration/from-v1.html +90 -15
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
- nautobot/project-static/docs/development/core/application-registry.html +87 -12
- nautobot/project-static/docs/development/core/best-practices.html +88 -13
- nautobot/project-static/docs/development/core/bootstrap-ui.html +88 -13
- nautobot/project-static/docs/development/core/caching.html +87 -12
- nautobot/project-static/docs/development/core/controllers.html +87 -12
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +94 -19
- nautobot/project-static/docs/development/core/generic-views.html +87 -12
- nautobot/project-static/docs/development/core/getting-started.html +89 -14
- nautobot/project-static/docs/development/core/homepage.html +87 -12
- nautobot/project-static/docs/development/core/index.html +88 -13
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +90 -15
- nautobot/project-static/docs/development/core/model-checklist.html +88 -13
- nautobot/project-static/docs/development/core/model-features.html +87 -12
- nautobot/project-static/docs/development/core/natural-keys.html +87 -12
- nautobot/project-static/docs/development/core/navigation-menu.html +88 -13
- nautobot/project-static/docs/development/core/release-checklist.html +88 -13
- nautobot/project-static/docs/development/core/role-internals.html +87 -12
- nautobot/project-static/docs/development/core/settings.html +88 -13
- nautobot/project-static/docs/development/core/style-guide.html +91 -16
- nautobot/project-static/docs/development/core/templates.html +88 -13
- nautobot/project-static/docs/development/core/testing.html +87 -12
- nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
- nautobot/project-static/docs/development/core/user-preferences.html +87 -12
- nautobot/project-static/docs/development/index.html +87 -12
- nautobot/project-static/docs/development/jobs/index.html +95 -13
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +90 -14
- nautobot/project-static/docs/index.html +90 -14
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +89 -14
- nautobot/project-static/docs/overview/design_philosophy.html +87 -12
- nautobot/project-static/docs/release-notes/index.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.1.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.2.html +90 -15
- nautobot/project-static/docs/release-notes/version-1.3.html +88 -13
- nautobot/project-static/docs/release-notes/version-1.4.html +104 -29
- nautobot/project-static/docs/release-notes/version-1.5.html +95 -20
- nautobot/project-static/docs/release-notes/version-1.6.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.0.html +97 -22
- nautobot/project-static/docs/release-notes/version-2.1.html +94 -19
- nautobot/project-static/docs/release-notes/version-2.2.html +88 -13
- nautobot/project-static/docs/release-notes/version-2.3.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.4.html +465 -12
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +296 -288
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +91 -16
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +88 -13
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +95 -20
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +90 -15
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +88 -13
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +91 -16
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +102 -27
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +89 -14
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +93 -18
- nautobot/project-static/docs/user-guide/administration/installation/services.html +88 -13
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
- nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9844 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +88 -13
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +98 -20
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +99 -24
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +188 -30
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
- nautobot/project-static/docs/user-guide/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +88 -13
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +90 -15
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +89 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +93 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
- nautobot/project-static/js/dropdown.js +28 -0
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
- nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
- nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
- nautobot/tenancy/tests/test_views.py +5 -1
- nautobot/tenancy/urls.py +7 -79
- nautobot/tenancy/views.py +51 -80
- nautobot/virtualization/templates/virtualization/cluster.html +1 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
- nautobot/wireless/api/serializers.py +6 -1
- nautobot/wireless/api/views.py +3 -3
- nautobot/wireless/tests/test_api.py +5 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/METADATA +12 -12
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/RECORD +459 -443
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
- /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/WHEEL +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import datetime, timedelta, timezone
|
|
2
2
|
import os
|
|
3
|
+
import shutil
|
|
3
4
|
import tempfile
|
|
4
5
|
from unittest import expectedFailure, mock
|
|
5
6
|
import uuid
|
|
@@ -17,6 +18,7 @@ from django.test import override_settings
|
|
|
17
18
|
from django.test.utils import isolate_apps
|
|
18
19
|
from django.utils.timezone import get_default_timezone, now
|
|
19
20
|
from django_celery_beat.tzcrontab import TzAwareCrontab
|
|
21
|
+
from git import GitCommandError
|
|
20
22
|
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
|
21
23
|
import time_machine
|
|
22
24
|
|
|
@@ -48,6 +50,7 @@ from nautobot.extras.constants import (
|
|
|
48
50
|
JOB_LOG_MAX_LOG_OBJECT_LENGTH,
|
|
49
51
|
JOB_OVERRIDABLE_FIELDS,
|
|
50
52
|
)
|
|
53
|
+
from nautobot.extras.datasources.registry import get_datasource_contents
|
|
51
54
|
from nautobot.extras.jobs import get_job
|
|
52
55
|
from nautobot.extras.models import (
|
|
53
56
|
ComputedField,
|
|
@@ -83,6 +86,7 @@ from nautobot.extras.models import (
|
|
|
83
86
|
from nautobot.extras.models.statuses import StatusModel
|
|
84
87
|
from nautobot.extras.registry import registry
|
|
85
88
|
from nautobot.extras.secrets.exceptions import SecretParametersError, SecretProviderError, SecretValueNotFoundError
|
|
89
|
+
from nautobot.extras.tests.git_helper import create_and_populate_git_repository
|
|
86
90
|
from nautobot.ipam.models import IPAddress
|
|
87
91
|
from nautobot.tenancy.models import Tenant
|
|
88
92
|
from nautobot.virtualization.models import (
|
|
@@ -1072,6 +1076,285 @@ class GitRepositoryTest(ModelTestCases.BaseModelTestCase):
|
|
|
1072
1076
|
self.repo.remote_url = "http://some-private-host/example.git"
|
|
1073
1077
|
self.repo.validated_save()
|
|
1074
1078
|
|
|
1079
|
+
def test_clone_to_directory_context_manager(self):
|
|
1080
|
+
"""Confirm that the clone_to_directory_context() context manager method works as expected."""
|
|
1081
|
+
try:
|
|
1082
|
+
specified_path = tempfile.mkdtemp()
|
|
1083
|
+
self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
|
1084
|
+
create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
|
|
1085
|
+
self.repo_slug = "new_git_repo"
|
|
1086
|
+
self.repo = GitRepository(
|
|
1087
|
+
name="New Git Repository",
|
|
1088
|
+
slug=self.repo_slug,
|
|
1089
|
+
remote_url="file://"
|
|
1090
|
+
+ self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
|
|
1091
|
+
branch="main",
|
|
1092
|
+
# Provide everything we know we can provide
|
|
1093
|
+
provided_contents=[
|
|
1094
|
+
entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
|
|
1095
|
+
],
|
|
1096
|
+
)
|
|
1097
|
+
self.repo.save()
|
|
1098
|
+
with self.subTest("Clone a repository with no path argument provided"):
|
|
1099
|
+
with self.repo.clone_to_directory_context() as path:
|
|
1100
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1101
|
+
self.assertTrue(path.startswith(tempfile.gettempdir()))
|
|
1102
|
+
self.assertTrue(os.path.exists(path))
|
|
1103
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1104
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1105
|
+
self.assertFalse(os.path.exists(path))
|
|
1106
|
+
|
|
1107
|
+
with self.subTest("Clone a repository with a path argument provided"):
|
|
1108
|
+
with self.repo.clone_to_directory_context(path=specified_path) as path:
|
|
1109
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1110
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1111
|
+
self.assertTrue(os.path.exists(path))
|
|
1112
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1113
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1114
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1115
|
+
self.assertFalse(os.path.exists(path))
|
|
1116
|
+
|
|
1117
|
+
with self.subTest("Clone a repository with the branch argument provided"):
|
|
1118
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="main") as path:
|
|
1119
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1120
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1121
|
+
self.assertTrue(os.path.exists(path))
|
|
1122
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1123
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1124
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1125
|
+
self.assertFalse(os.path.exists(path))
|
|
1126
|
+
|
|
1127
|
+
with self.subTest("Clone a repository with non-default branch provided"):
|
|
1128
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="empty-repo") as path:
|
|
1129
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1130
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1131
|
+
self.assertTrue(os.path.exists(path))
|
|
1132
|
+
# empty-repo should contain no files
|
|
1133
|
+
self.assertFalse(os.path.exists(path + "/config_context_schemas"))
|
|
1134
|
+
self.assertFalse(os.path.exists(path + "/config_contexts"))
|
|
1135
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1136
|
+
self.assertFalse(os.path.exists(path))
|
|
1137
|
+
|
|
1138
|
+
with self.subTest("Clone a repository with divergent branch provided"):
|
|
1139
|
+
with self.repo.clone_to_directory_context(path=specified_path, branch="divergent-branch") as path:
|
|
1140
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1141
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1142
|
+
self.assertTrue(os.path.exists(path))
|
|
1143
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1144
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1145
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1146
|
+
self.assertFalse(os.path.exists(path))
|
|
1147
|
+
|
|
1148
|
+
with self.subTest("Clone a repository with the head argument provided"):
|
|
1149
|
+
with self.repo.clone_to_directory_context(path=specified_path, head="valid-files") as path:
|
|
1150
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1151
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1152
|
+
self.assertTrue(os.path.exists(path))
|
|
1153
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
|
|
1154
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
|
|
1155
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1156
|
+
self.assertFalse(os.path.exists(path))
|
|
1157
|
+
|
|
1158
|
+
with self.subTest("Clone a repository with depth argument provided"):
|
|
1159
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1) as path:
|
|
1160
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1161
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1162
|
+
self.assertTrue(os.path.exists(path))
|
|
1163
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1164
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1165
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1166
|
+
self.assertFalse(os.path.exists(path))
|
|
1167
|
+
|
|
1168
|
+
with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
|
|
1169
|
+
with self.repo.clone_to_directory_context(
|
|
1170
|
+
path=specified_path, depth=1, head="divergent-branch-tag"
|
|
1171
|
+
) as path:
|
|
1172
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1173
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1174
|
+
self.assertTrue(os.path.exists(path))
|
|
1175
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1176
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1177
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1178
|
+
self.assertFalse(os.path.exists(path))
|
|
1179
|
+
|
|
1180
|
+
with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
|
|
1181
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
|
|
1182
|
+
# Shallow copy a repo should only have the latest commit
|
|
1183
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1, head="valid-files") as path:
|
|
1184
|
+
pass
|
|
1185
|
+
|
|
1186
|
+
with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
|
|
1187
|
+
with self.repo.clone_to_directory_context(path=specified_path, depth=1, branch="main") as path:
|
|
1188
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1189
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1190
|
+
self.assertTrue(os.path.exists(path))
|
|
1191
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1192
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1193
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1194
|
+
self.assertFalse(os.path.exists(path))
|
|
1195
|
+
|
|
1196
|
+
with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
|
|
1197
|
+
with self.repo.clone_to_directory_context(
|
|
1198
|
+
path=specified_path, depth=1, branch="divergent-branch"
|
|
1199
|
+
) as path:
|
|
1200
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1201
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1202
|
+
self.assertTrue(os.path.exists(path))
|
|
1203
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1204
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1205
|
+
# Temp directory is cleaned up after the context manager exits
|
|
1206
|
+
self.assertFalse(os.path.exists(path))
|
|
1207
|
+
|
|
1208
|
+
with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
|
|
1209
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
|
|
1210
|
+
with self.repo.clone_to_directory_context(path=specified_path, head="non-existent") as path:
|
|
1211
|
+
pass
|
|
1212
|
+
|
|
1213
|
+
with self.subTest("Assert a value error is raised when branch and head are both provided"):
|
|
1214
|
+
with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
|
|
1215
|
+
with self.repo.clone_to_directory_context(branch="main", head="valid-files") as path:
|
|
1216
|
+
pass
|
|
1217
|
+
finally:
|
|
1218
|
+
shutil.rmtree(specified_path, ignore_errors=True)
|
|
1219
|
+
shutil.rmtree(self.tempdir.name, ignore_errors=True)
|
|
1220
|
+
|
|
1221
|
+
def test_clone_to_directory_helper_methods(self):
|
|
1222
|
+
"""Confirm that the clone_to_directory()/cleanup_cloned_directory() methods work as expected."""
|
|
1223
|
+
try:
|
|
1224
|
+
specified_path = tempfile.mkdtemp()
|
|
1225
|
+
self.tempdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
|
1226
|
+
create_and_populate_git_repository(self.tempdir.name, divergent_branch="divergent-branch")
|
|
1227
|
+
self.repo_slug = "new_git_repo"
|
|
1228
|
+
self.repo = GitRepository(
|
|
1229
|
+
name="New Git Repository",
|
|
1230
|
+
slug=self.repo_slug,
|
|
1231
|
+
remote_url="file://"
|
|
1232
|
+
+ self.tempdir.name, # file:// URLs aren't permitted normally, but very useful here!
|
|
1233
|
+
branch="main",
|
|
1234
|
+
# Provide everything we know we can provide
|
|
1235
|
+
provided_contents=[
|
|
1236
|
+
entry.content_identifier for entry in get_datasource_contents("extras.gitrepository")
|
|
1237
|
+
],
|
|
1238
|
+
)
|
|
1239
|
+
self.repo.save()
|
|
1240
|
+
with self.subTest("Clone a repository with no path argument provided"):
|
|
1241
|
+
path = self.repo.clone_to_directory()
|
|
1242
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1243
|
+
self.assertTrue(path.startswith(tempfile.gettempdir()))
|
|
1244
|
+
self.assertTrue(os.path.exists(path))
|
|
1245
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1246
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1247
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1248
|
+
self.assertFalse(os.path.exists(path))
|
|
1249
|
+
|
|
1250
|
+
with self.subTest("Clone a repository with a path argument provided"):
|
|
1251
|
+
path = self.repo.clone_to_directory(path=specified_path)
|
|
1252
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1253
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1254
|
+
self.assertTrue(os.path.exists(path))
|
|
1255
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1256
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1257
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1258
|
+
self.assertFalse(os.path.exists(path))
|
|
1259
|
+
|
|
1260
|
+
with self.subTest("Clone a repository with the branch argument provided"):
|
|
1261
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="main")
|
|
1262
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1263
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1264
|
+
self.assertTrue(os.path.exists(path))
|
|
1265
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1266
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema2.json"))
|
|
1267
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1268
|
+
self.assertFalse(os.path.exists(path))
|
|
1269
|
+
|
|
1270
|
+
with self.subTest("Clone a repository with non-default branch provided"):
|
|
1271
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="empty-repo")
|
|
1272
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1273
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1274
|
+
self.assertTrue(os.path.exists(path))
|
|
1275
|
+
self.assertFalse(os.path.exists(path + "/config_context_schemas"))
|
|
1276
|
+
self.assertFalse(os.path.exists(path + "/config_contexts"))
|
|
1277
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1278
|
+
self.assertFalse(os.path.exists(path))
|
|
1279
|
+
|
|
1280
|
+
with self.subTest("Clone a repository with divergent branch provided"):
|
|
1281
|
+
path = self.repo.clone_to_directory(path=specified_path, branch="divergent-branch")
|
|
1282
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1283
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1284
|
+
self.assertTrue(os.path.exists(path))
|
|
1285
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas"))
|
|
1286
|
+
self.assertTrue(os.path.exists(path + "/config_contexts"))
|
|
1287
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1288
|
+
self.assertFalse(os.path.exists(path))
|
|
1289
|
+
|
|
1290
|
+
with self.subTest("Clone a repository with the head argument provided"):
|
|
1291
|
+
path = self.repo.clone_to_directory(path=specified_path, head="valid-files")
|
|
1292
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1293
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1294
|
+
self.assertTrue(os.path.exists(path))
|
|
1295
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/schema-1.yaml"))
|
|
1296
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/context.yaml"))
|
|
1297
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1298
|
+
self.assertFalse(os.path.exists(path))
|
|
1299
|
+
|
|
1300
|
+
with self.subTest("Clone a repository with depth argument provided"):
|
|
1301
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1)
|
|
1302
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1303
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1304
|
+
self.assertTrue(os.path.exists(path))
|
|
1305
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1306
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1307
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1308
|
+
self.assertFalse(os.path.exists(path))
|
|
1309
|
+
|
|
1310
|
+
with self.subTest("Clone a shallow repository with depth and valid head arguments provided"):
|
|
1311
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, head="divergent-branch-tag")
|
|
1312
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1313
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1314
|
+
self.assertTrue(os.path.exists(path))
|
|
1315
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1316
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1317
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1318
|
+
self.assertFalse(os.path.exists(path))
|
|
1319
|
+
|
|
1320
|
+
with self.subTest("Clone a shallow repository with depth and invalid head arguments provided"):
|
|
1321
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name valid-files"):
|
|
1322
|
+
# Shallow copy a repo should only have the latest commit
|
|
1323
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, head="valid-files")
|
|
1324
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1325
|
+
self.assertFalse(os.path.exists(path))
|
|
1326
|
+
|
|
1327
|
+
with self.subTest("Clone a shallow repository with depth and valid branch arguments provided"):
|
|
1328
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="main")
|
|
1329
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1330
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1331
|
+
self.assertTrue(os.path.exists(path))
|
|
1332
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1333
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1334
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1335
|
+
self.assertFalse(os.path.exists(path))
|
|
1336
|
+
|
|
1337
|
+
with self.subTest("Clone a shallow repository with depth and divergent branch arguments provided"):
|
|
1338
|
+
path = self.repo.clone_to_directory(path=specified_path, depth=1, branch="divergent-branch")
|
|
1339
|
+
# assert that the temporary directory was created in the expected location i.e. /tmp/
|
|
1340
|
+
self.assertTrue(path.startswith(specified_path))
|
|
1341
|
+
self.assertTrue(os.path.exists(path))
|
|
1342
|
+
self.assertTrue(os.path.exists(path + "/config_context_schemas/badschema1.json"))
|
|
1343
|
+
self.assertTrue(os.path.exists(path + "/config_contexts/badcontext2.json"))
|
|
1344
|
+
self.repo.cleanup_cloned_directory(path)
|
|
1345
|
+
self.assertFalse(os.path.exists(path))
|
|
1346
|
+
|
|
1347
|
+
with self.subTest("Assert a GitCommandError is raised when an invalid commit hash is provided"):
|
|
1348
|
+
with self.assertRaisesRegex(GitCommandError, "malformed object name non-existent"):
|
|
1349
|
+
path = self.repo.clone_to_directory(path=specified_path, head="non-existent")
|
|
1350
|
+
|
|
1351
|
+
with self.subTest("Assert a ValuError is raised when branch and head are both provided"):
|
|
1352
|
+
with self.assertRaisesRegex(ValueError, "Cannot specify both branch and head"):
|
|
1353
|
+
path = self.repo.clone_to_directory(branch="main", head="valid-files")
|
|
1354
|
+
finally:
|
|
1355
|
+
shutil.rmtree(specified_path, ignore_errors=True)
|
|
1356
|
+
shutil.rmtree(self.tempdir.name, ignore_errors=True)
|
|
1357
|
+
|
|
1075
1358
|
|
|
1076
1359
|
class JobModelTest(ModelTestCases.BaseModelTestCase):
|
|
1077
1360
|
"""
|
|
@@ -4,13 +4,34 @@ import uuid
|
|
|
4
4
|
from django.core.cache import cache
|
|
5
5
|
|
|
6
6
|
from nautobot.core.testing import TestCase
|
|
7
|
+
from nautobot.dcim.models import Device, LocationType
|
|
7
8
|
from nautobot.extras.choices import JobQueueTypeChoices
|
|
8
9
|
from nautobot.extras.models import JobQueue
|
|
9
10
|
from nautobot.extras.registry import registry
|
|
10
|
-
from nautobot.extras.utils import
|
|
11
|
+
from nautobot.extras.utils import (
|
|
12
|
+
get_base_template,
|
|
13
|
+
get_celery_queues,
|
|
14
|
+
get_worker_count,
|
|
15
|
+
populate_model_features_registry,
|
|
16
|
+
)
|
|
17
|
+
from nautobot.users.models import Token
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
class UtilsTestCase(TestCase):
|
|
21
|
+
def test_get_base_template(self):
|
|
22
|
+
with self.subTest("explicitly specified base_template always wins"):
|
|
23
|
+
self.assertEqual(get_base_template("dcim/device/base.html", Device), "dcim/device/base.html")
|
|
24
|
+
|
|
25
|
+
with self.subTest("<model>.html wins over <model>_retrieve.html"):
|
|
26
|
+
# TODO: why do we even have both locationtype.html and locationtype_retrieve.html?
|
|
27
|
+
self.assertEqual(get_base_template(None, LocationType), "dcim/locationtype.html")
|
|
28
|
+
|
|
29
|
+
with self.subTest("<model>_retrieve.html is used if present"):
|
|
30
|
+
self.assertEqual(get_base_template(None, JobQueue), "extras/jobqueue_retrieve.html")
|
|
31
|
+
|
|
32
|
+
with self.subTest("generic/object_retrieve.html is used as a fallback"):
|
|
33
|
+
self.assertEqual(get_base_template(None, Token), "generic/object_retrieve.html")
|
|
34
|
+
|
|
14
35
|
@mock.patch("celery.app.control.Inspect.active_queues")
|
|
15
36
|
def test_get_celery_queues(self, mock_active_queues):
|
|
16
37
|
with self.subTest("No queues"):
|
|
@@ -820,7 +820,8 @@ class DynamicGroupTestCase(
|
|
|
820
820
|
return super()._get_queryset().filter(group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER) # TODO
|
|
821
821
|
|
|
822
822
|
def test_get_object_with_permission(self):
|
|
823
|
-
|
|
823
|
+
location_ct = ContentType.objects.get_for_model(Location)
|
|
824
|
+
instance = self._get_queryset().exclude(content_type=location_ct).first()
|
|
824
825
|
# Add view permissions for the group's members:
|
|
825
826
|
self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
|
|
826
827
|
|
|
@@ -831,6 +832,18 @@ class DynamicGroupTestCase(
|
|
|
831
832
|
for member in instance.members:
|
|
832
833
|
self.assertIn(str(member.pk), response_body)
|
|
833
834
|
|
|
835
|
+
# Test accessing DynamicGroup detail view with a different content type, more specifically, TreeModel
|
|
836
|
+
# https://github.com/nautobot/nautobot/issues/6806
|
|
837
|
+
tree_model_dg = DynamicGroup.objects.create(name="DG 4", content_type=location_ct)
|
|
838
|
+
# Add view permissions for the group's members:
|
|
839
|
+
self.add_permissions(get_permission_for_model(tree_model_dg.content_type.model_class(), "view"))
|
|
840
|
+
response = self.client.get(tree_model_dg.get_absolute_url())
|
|
841
|
+
self.assertHttpStatus(response, 200)
|
|
842
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
843
|
+
# Check that the "members" table in the detail view includes all appropriate member objects
|
|
844
|
+
for member in tree_model_dg.members:
|
|
845
|
+
self.assertIn(str(member.pk), response_body)
|
|
846
|
+
|
|
834
847
|
def test_get_object_with_constrained_permission(self):
|
|
835
848
|
instance = self._get_queryset().first()
|
|
836
849
|
# Add view permission for one of the group's members but not the others:
|
|
@@ -1306,19 +1319,18 @@ class SavedViewTest(ModelViewTestCase):
|
|
|
1306
1319
|
|
|
1307
1320
|
model = SavedView
|
|
1308
1321
|
|
|
1309
|
-
def get_view_url_for_saved_view(self, saved_view, action="detail"):
|
|
1322
|
+
def get_view_url_for_saved_view(self, saved_view=None, action="detail"):
|
|
1310
1323
|
"""
|
|
1311
1324
|
Since saved view detail url redirects, we need to manually construct its detail url
|
|
1312
1325
|
to test the content of its response.
|
|
1313
1326
|
"""
|
|
1314
|
-
|
|
1315
|
-
pk = saved_view.pk
|
|
1327
|
+
url = ""
|
|
1316
1328
|
|
|
1317
|
-
if action == "detail":
|
|
1318
|
-
url = reverse(view) + f"?saved_view={pk}"
|
|
1319
|
-
elif action == "edit":
|
|
1329
|
+
if action == "detail" and saved_view:
|
|
1330
|
+
url = reverse(saved_view.view) + f"?saved_view={saved_view.pk}"
|
|
1331
|
+
elif action == "edit" and saved_view:
|
|
1320
1332
|
url = saved_view.get_absolute_url() + "update-config/"
|
|
1321
|
-
|
|
1333
|
+
elif action == "create":
|
|
1322
1334
|
url = reverse("extras:savedview_add")
|
|
1323
1335
|
|
|
1324
1336
|
return url
|
|
@@ -1411,7 +1423,14 @@ class SavedViewTest(ModelViewTestCase):
|
|
|
1411
1423
|
|
|
1412
1424
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1413
1425
|
def test_update_saved_view_as_owner(self):
|
|
1414
|
-
|
|
1426
|
+
view_name = "dcim:location_list"
|
|
1427
|
+
instance = SavedView.objects.create(
|
|
1428
|
+
name="Location Saved View",
|
|
1429
|
+
owner=self.user,
|
|
1430
|
+
view=view_name,
|
|
1431
|
+
is_global_default=True,
|
|
1432
|
+
)
|
|
1433
|
+
|
|
1415
1434
|
update_query_strings = ["per_page=12", "&status=active", "&name=new_name_filter", "&sort=name"]
|
|
1416
1435
|
update_url = self.get_view_url_for_saved_view(instance, "edit") + "?" + "".join(update_query_strings)
|
|
1417
1436
|
# Try update the saved view with the same user as the owner of the saved view
|
|
@@ -1543,6 +1562,62 @@ class SavedViewTest(ModelViewTestCase):
|
|
|
1543
1562
|
# Assert that Location List View got redirected to Saved View set as user default
|
|
1544
1563
|
self.assertBodyContains(response, "<strong>User Location Default View</strong>", html=True)
|
|
1545
1564
|
|
|
1565
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1566
|
+
def test_filtered_view_precedes_global_default(self):
|
|
1567
|
+
view_name = "dcim:location_list"
|
|
1568
|
+
# Global saved view that will show Floor type locations only.
|
|
1569
|
+
SavedView.objects.create(
|
|
1570
|
+
name="Global Location Default View",
|
|
1571
|
+
owner=self.user,
|
|
1572
|
+
view=view_name,
|
|
1573
|
+
is_global_default=True,
|
|
1574
|
+
config={
|
|
1575
|
+
"filter_params": {
|
|
1576
|
+
"location_type": ["Floor"],
|
|
1577
|
+
}
|
|
1578
|
+
},
|
|
1579
|
+
)
|
|
1580
|
+
response = self.client.get(reverse(view_name) + "?location_type=Campus", follow=True)
|
|
1581
|
+
# Assert that the user is not redirected to the global default view
|
|
1582
|
+
# But instead redirected to the filtered view
|
|
1583
|
+
self.assertNotIn(
|
|
1584
|
+
"<strong>Global Location Default View</strong>",
|
|
1585
|
+
extract_page_body(response.content.decode(response.charset)),
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
# Floor type locations (Floor-<number>) should not be visible in the response
|
|
1589
|
+
self.assertNotIn(
|
|
1590
|
+
"Floor-",
|
|
1591
|
+
extract_page_body(response.content.decode(response.charset)),
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1594
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1595
|
+
def test_filtered_view_precedes_user_default(self):
|
|
1596
|
+
view_name = "dcim:location_list"
|
|
1597
|
+
# User saved view that will show Floor type locations only.
|
|
1598
|
+
sv = SavedView.objects.create(
|
|
1599
|
+
name="User Location Default View",
|
|
1600
|
+
owner=self.user,
|
|
1601
|
+
view=view_name,
|
|
1602
|
+
config={
|
|
1603
|
+
"filter_params": {
|
|
1604
|
+
"location_type": ["Floor"],
|
|
1605
|
+
}
|
|
1606
|
+
},
|
|
1607
|
+
)
|
|
1608
|
+
UserSavedViewAssociation.objects.create(user=self.user, saved_view=sv, view_name=sv.view)
|
|
1609
|
+
response = self.client.get(reverse(view_name) + "?location_type=Campus", follow=True)
|
|
1610
|
+
# Assert that the user is not redirected to the user default view
|
|
1611
|
+
# But instead redirected to the filtered view
|
|
1612
|
+
self.assertNotIn(
|
|
1613
|
+
"<strong>User Location Default View</strong>", extract_page_body(response.content.decode(response.charset))
|
|
1614
|
+
)
|
|
1615
|
+
# Floor type locations (Floor-<number>) should not be visible in the response
|
|
1616
|
+
self.assertNotIn(
|
|
1617
|
+
"Floor-",
|
|
1618
|
+
extract_page_body(response.content.decode(response.charset)),
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1546
1621
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
1547
1622
|
def test_is_shared(self):
|
|
1548
1623
|
view_name = "dcim:location_list"
|
|
@@ -1568,6 +1643,119 @@ class SavedViewTest(ModelViewTestCase):
|
|
|
1568
1643
|
self.assertIn(str(sv_shared.pk), response_body, msg=response_body)
|
|
1569
1644
|
self.assertNotIn(str(sv_not_shared.pk), response_body, msg=response_body)
|
|
1570
1645
|
|
|
1646
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1647
|
+
def test_create_saved_views_contain_boolean_filter_params(self):
|
|
1648
|
+
"""
|
|
1649
|
+
Test the entire Save View workflow from creating a Saved View to rendering the View with boolean filter parameters.
|
|
1650
|
+
"""
|
|
1651
|
+
with self.subTest("Create job Saved View with boolean filter parameters"):
|
|
1652
|
+
view_name = "extras:job_list"
|
|
1653
|
+
app_label = view_name.split(":")[0]
|
|
1654
|
+
model_name = view_name.split(":")[1].split("_")[0]
|
|
1655
|
+
self.add_permissions(f"{app_label}.view_{model_name}")
|
|
1656
|
+
create_query_strings = [
|
|
1657
|
+
"&hidden=True",
|
|
1658
|
+
]
|
|
1659
|
+
create_url = self.get_view_url_for_saved_view(action="create")
|
|
1660
|
+
sv_name = "Hidden Jobs"
|
|
1661
|
+
request = {
|
|
1662
|
+
"path": create_url,
|
|
1663
|
+
"data": post_data({"name": sv_name, "view": f"{view_name}", "params": "".join(create_query_strings)}),
|
|
1664
|
+
}
|
|
1665
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1666
|
+
instance = SavedView.objects.get(name=sv_name)
|
|
1667
|
+
hidden_job = Job.objects.get(name="Example hidden job")
|
|
1668
|
+
hidden_job.description = "I should not show in the UI!"
|
|
1669
|
+
hidden_job.save()
|
|
1670
|
+
self.assertEqual(instance.config["filter_params"]["hidden"], "True")
|
|
1671
|
+
response = self.client.get(reverse(view_name) + "?saved_view=" + str(instance.pk), follow=True)
|
|
1672
|
+
# Assert that Job List View rendered with the boolean filter parameter without error
|
|
1673
|
+
self.assertHttpStatus(response, 200)
|
|
1674
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
1675
|
+
self.assertIn(str(instance.pk), response_body, msg=response_body)
|
|
1676
|
+
self.assertBodyContains(response, f"<strong>{sv_name}</strong>", html=True)
|
|
1677
|
+
# This is the description
|
|
1678
|
+
self.assertBodyContains(response, "I should not show in the UI!", html=True)
|
|
1679
|
+
|
|
1680
|
+
with self.subTest("Create device Saved View with boolean filter parameters"):
|
|
1681
|
+
view_name = "dcim:device_list"
|
|
1682
|
+
app_label = view_name.split(":")[0]
|
|
1683
|
+
model_name = view_name.split(":")[1].split("_")[0]
|
|
1684
|
+
self.add_permissions(f"{app_label}.view_{model_name}")
|
|
1685
|
+
create_query_strings = [
|
|
1686
|
+
"&per_page=12",
|
|
1687
|
+
"&has_primary_ip=True",
|
|
1688
|
+
"&sort=name",
|
|
1689
|
+
]
|
|
1690
|
+
create_url = self.get_view_url_for_saved_view(action="create")
|
|
1691
|
+
sv_name = "Devices with primary ips"
|
|
1692
|
+
request = {
|
|
1693
|
+
"path": create_url,
|
|
1694
|
+
"data": post_data({"name": sv_name, "view": f"{view_name}", "params": "".join(create_query_strings)}),
|
|
1695
|
+
}
|
|
1696
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1697
|
+
instance = SavedView.objects.get(name=sv_name)
|
|
1698
|
+
self.assertEqual(instance.config["pagination_count"], 12)
|
|
1699
|
+
self.assertEqual(instance.config["filter_params"]["has_primary_ip"], "True")
|
|
1700
|
+
self.assertEqual(instance.config["sort_order"], ["name"])
|
|
1701
|
+
response = self.client.get(reverse(view_name) + "?saved_view=" + str(instance.pk), follow=True)
|
|
1702
|
+
# Assert that Job List View rendered with the boolean filter parameter without error
|
|
1703
|
+
self.assertHttpStatus(response, 200)
|
|
1704
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
1705
|
+
self.assertIn(str(instance.pk), response_body, msg=response_body)
|
|
1706
|
+
self.assertBodyContains(response, f"<strong>{sv_name}</strong>", html=True)
|
|
1707
|
+
|
|
1708
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1709
|
+
def test_update_saved_view_contain_boolean_filter_params(self):
|
|
1710
|
+
with self.subTest("Update job Saved View with boolean filter parameters"):
|
|
1711
|
+
view_name = "extras:job_list"
|
|
1712
|
+
sv_name = "Non-hidden jobs"
|
|
1713
|
+
instance = SavedView.objects.create(
|
|
1714
|
+
name=sv_name,
|
|
1715
|
+
owner=self.user,
|
|
1716
|
+
view=view_name,
|
|
1717
|
+
)
|
|
1718
|
+
update_query_strings = ["hidden=False"]
|
|
1719
|
+
update_url = self.get_view_url_for_saved_view(instance, "edit") + "?" + "".join(update_query_strings)
|
|
1720
|
+
# Try update the saved view with the same user as the owner of the saved view
|
|
1721
|
+
instance.owner.is_active = True
|
|
1722
|
+
instance.owner.save()
|
|
1723
|
+
self.client.force_login(instance.owner)
|
|
1724
|
+
response = self.client.get(update_url)
|
|
1725
|
+
self.assertHttpStatus(response, 302)
|
|
1726
|
+
instance.refresh_from_db()
|
|
1727
|
+
self.assertEqual(instance.config["filter_params"]["hidden"], "False")
|
|
1728
|
+
response = self.client.get(reverse(view_name) + "?saved_view=" + str(instance.pk), follow=True)
|
|
1729
|
+
# Assert that Job List View rendered with the boolean filter parameter without error
|
|
1730
|
+
self.assertHttpStatus(response, 200)
|
|
1731
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
1732
|
+
self.assertNotIn("Example hidden job", response_body, msg=response_body)
|
|
1733
|
+
self.assertBodyContains(response, f"<strong>{sv_name}</strong>", html=True)
|
|
1734
|
+
|
|
1735
|
+
with self.subTest("Update device Saved View with boolean filter parameters"):
|
|
1736
|
+
view_name = "dcim:device_list"
|
|
1737
|
+
sv_name = "Devices with no primary ips"
|
|
1738
|
+
instance = SavedView.objects.create(
|
|
1739
|
+
name=sv_name,
|
|
1740
|
+
owner=self.user,
|
|
1741
|
+
view=view_name,
|
|
1742
|
+
)
|
|
1743
|
+
update_query_strings = ["has_primary_ip=False"]
|
|
1744
|
+
update_url = self.get_view_url_for_saved_view(instance, "edit") + "?" + "".join(update_query_strings)
|
|
1745
|
+
# Try update the saved view with the same user as the owner of the saved view
|
|
1746
|
+
instance.owner.is_active = True
|
|
1747
|
+
instance.owner.save()
|
|
1748
|
+
self.client.force_login(instance.owner)
|
|
1749
|
+
response = self.client.get(update_url)
|
|
1750
|
+
self.assertHttpStatus(response, 302)
|
|
1751
|
+
instance.refresh_from_db()
|
|
1752
|
+
self.assertEqual(instance.config["filter_params"]["has_primary_ip"], "False")
|
|
1753
|
+
response = self.client.get(reverse(view_name) + "?saved_view=" + str(instance.pk), follow=True)
|
|
1754
|
+
# Assert that Job List View rendered with the boolean filter parameter without error
|
|
1755
|
+
self.assertHttpStatus(response, 200)
|
|
1756
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
1757
|
+
self.assertBodyContains(response, f"<strong>{sv_name}</strong>", html=True)
|
|
1758
|
+
|
|
1571
1759
|
|
|
1572
1760
|
# Not a full-fledged PrimaryObjectViewTestCase as there's no BulkEditView for Secrets
|
|
1573
1761
|
class SecretTestCase(
|