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
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
onerror="window.location='{% url 'media_failure' %}?filename=js/theme.js'"></script>
|
|
25
25
|
<script src="{% versioned_static 'js/table_sorting_indicator.js' %}"
|
|
26
26
|
onerror="window.location='{% url 'media_failure' %}?filename=js/table_sorting_indicator.js'"></script>
|
|
27
|
+
<script src="{% versioned_static 'js/dropdown.js' %}"
|
|
28
|
+
onerror="window.location='{% url 'media_failure' %}?filename=js/dropdown.js'"></script>
|
|
27
29
|
<script type="text/javascript">
|
|
28
30
|
var nautobot_api_path = "{% url 'api-root' %}";
|
|
29
31
|
var nautobot_csrf_token = "{{ csrf_token }}";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
|
|
2
|
+
<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}{% if widget.attrs.checked %} checked{% endif %}>
|
|
3
|
+
<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
|
|
4
|
+
{{ widget.input_text }}:{% endif %}
|
|
5
|
+
<input type="{{ widget.type }}" name="{{ widget.name }}" class="filestyle" data-placeholder="No file selected" {% include "django/forms/widgets/attrs.html" %}>
|
|
@@ -870,9 +870,7 @@ def saved_view_modal(
|
|
|
870
870
|
):
|
|
871
871
|
from nautobot.extras.forms import SavedViewModalForm
|
|
872
872
|
from nautobot.extras.models import SavedView
|
|
873
|
-
|
|
874
|
-
param_dict = {}
|
|
875
|
-
filters_applied = parse_qs(params)
|
|
873
|
+
from nautobot.extras.utils import fixup_filterset_query_params
|
|
876
874
|
|
|
877
875
|
sort_order = []
|
|
878
876
|
per_page = None
|
|
@@ -889,6 +887,8 @@ def saved_view_modal(
|
|
|
889
887
|
"table_changes_pending",
|
|
890
888
|
"clear_view",
|
|
891
889
|
]
|
|
890
|
+
param_dict = {}
|
|
891
|
+
filters_applied = fixup_filterset_query_params(parse_qs(params), view, non_filter_params)
|
|
892
892
|
|
|
893
893
|
view_class = lookup.get_view_for_model(model, "List")
|
|
894
894
|
table_name = None
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from typing import Any, Optional
|
|
2
3
|
|
|
3
4
|
from django.conf import settings
|
|
4
5
|
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
|
6
|
+
from django.db.models import Model
|
|
5
7
|
from django.test import override_settings, tag
|
|
6
8
|
from django.urls import reverse
|
|
7
9
|
from django.utils.functional import classproperty
|
|
@@ -16,7 +18,7 @@ from nautobot.core import testing
|
|
|
16
18
|
SELENIUM_URL = os.getenv("NAUTOBOT_SELENIUM_URL", "http://localhost:4444/wd/hub")
|
|
17
19
|
|
|
18
20
|
# Hostname used by Selenium client to talk to Nautobot
|
|
19
|
-
SELENIUM_HOST = os.getenv("NAUTOBOT_SELENIUM_HOST", "
|
|
21
|
+
SELENIUM_HOST = os.getenv("NAUTOBOT_SELENIUM_HOST", "nautobot")
|
|
20
22
|
|
|
21
23
|
# Default login URL
|
|
22
24
|
LOGIN_URL = reverse(settings.LOGIN_URL)
|
|
@@ -28,23 +30,72 @@ class ObjectsListMixin:
|
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
def select_all_items(self):
|
|
31
|
-
|
|
33
|
+
"""
|
|
34
|
+
Click "toggle all" on top of the items table list to select all rows.
|
|
35
|
+
"""
|
|
36
|
+
self.browser.find_by_css("#object_list_form input.toggle").click()
|
|
32
37
|
|
|
33
38
|
def select_one_item(self):
|
|
34
|
-
|
|
39
|
+
"""
|
|
40
|
+
Click first row checkbox on items table list to select one row.
|
|
41
|
+
"""
|
|
42
|
+
self.browser.find_by_css('#object_list_form input[name="pk"]').click()
|
|
43
|
+
|
|
44
|
+
def set_per_page(self, per_page=1):
|
|
45
|
+
"""
|
|
46
|
+
Explicitly set the `per_page` parameter by navigating to the "current" page but with query param.
|
|
47
|
+
TODO: check if there are other query params and merge them
|
|
48
|
+
"""
|
|
49
|
+
self.browser.visit(f"{self.browser.url}?per_page={per_page}")
|
|
50
|
+
|
|
51
|
+
def select_all_items_from_all_pages(self):
|
|
52
|
+
"""
|
|
53
|
+
Selecting all the items from all pages by clicking "select all" on top of the items table list and then
|
|
54
|
+
select all on prompt that will show up.
|
|
55
|
+
"""
|
|
56
|
+
self.select_all_items()
|
|
57
|
+
self.browser.find_by_css("#select_all").click()
|
|
35
58
|
|
|
36
59
|
def click_bulk_delete(self):
|
|
60
|
+
"""
|
|
61
|
+
Click bulk delete from dropdown menu on bottom of the items table list.
|
|
62
|
+
"""
|
|
63
|
+
self.browser.execute_script(
|
|
64
|
+
"document.querySelector('#object_list_form button[type=\"submit\"]').scrollIntoView()"
|
|
65
|
+
)
|
|
37
66
|
self.browser.find_by_xpath(
|
|
38
67
|
'//*[@id="object_list_form"]//button[@type="submit"]/following-sibling::button[1]'
|
|
39
68
|
).click()
|
|
40
|
-
self.browser.
|
|
69
|
+
self.browser.find_by_css('#object_list_form button[name="_delete"]').click()
|
|
70
|
+
|
|
71
|
+
def click_bulk_delete_all(self):
|
|
72
|
+
"""
|
|
73
|
+
Click bulk delete all on prompt when selecting all items from all pages.
|
|
74
|
+
"""
|
|
75
|
+
self.click_button('#select_all_box button[name="_delete"]')
|
|
41
76
|
|
|
42
77
|
def click_bulk_edit(self):
|
|
43
|
-
|
|
78
|
+
"""
|
|
79
|
+
Click bulk edit button on bottom of the items table list.
|
|
80
|
+
"""
|
|
81
|
+
self.click_button('#object_list_form button[type="submit"]')
|
|
82
|
+
|
|
83
|
+
def click_bulk_edit_all(self):
|
|
84
|
+
"""
|
|
85
|
+
Click bulk edit all on prompt when selecting all items from all pages.
|
|
86
|
+
"""
|
|
87
|
+
self.click_button('#select_all_box button[name="_edit"]')
|
|
88
|
+
|
|
89
|
+
def click_table_link(self, row=1, column=2):
|
|
90
|
+
"""By default, tries to click column next to checkbox to go to the details page."""
|
|
91
|
+
self.browser.find_by_xpath(f'//*[@id="object_list_form"]//tbody/tr[{row}]/td[{column}]/a').click()
|
|
44
92
|
|
|
45
93
|
@property
|
|
46
94
|
def objects_list_visible_items(self):
|
|
47
|
-
|
|
95
|
+
"""
|
|
96
|
+
Calculating the visible items. Return 0 if there is no visible items.
|
|
97
|
+
"""
|
|
98
|
+
objects_table_container = self.browser.find_by_xpath('//*[@id="object_list_form"]')
|
|
48
99
|
try:
|
|
49
100
|
objects_table = objects_table_container.find_by_tag("tbody")
|
|
50
101
|
return len(objects_table.find_by_tag("tr"))
|
|
@@ -52,19 +103,48 @@ class ObjectsListMixin:
|
|
|
52
103
|
return 0
|
|
53
104
|
|
|
54
105
|
def apply_filter(self, field, value):
|
|
106
|
+
"""
|
|
107
|
+
Open filter dialog and apply select2 filters.
|
|
108
|
+
You can apply more values to the same filter, by calling this function with same name but different value.
|
|
109
|
+
"""
|
|
55
110
|
self.browser.find_by_xpath('//*[@id="id__filterbtn"]').click()
|
|
56
111
|
self.fill_filters_select2_field(field, value)
|
|
57
|
-
self.
|
|
112
|
+
self.click_button('#default-filter button[type="submit"]')
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ObjectDetailsMixin:
|
|
116
|
+
def assertPanelValue(self, panel_label, field_label, expected_value, exact_match=False):
|
|
117
|
+
"""
|
|
118
|
+
Find the proper panel and asserts if given value match rendered field value.
|
|
119
|
+
By default, it's not using the exact match, because on the UI we're often adding
|
|
120
|
+
additional tags, relationships or units.
|
|
121
|
+
"""
|
|
122
|
+
panel_xpath = f'//*[@id="main"]//div[@class="panel-heading"][contains(normalize-space(), "{panel_label}")]/following-sibling::table'
|
|
123
|
+
value = self.browser.find_by_xpath(f'{panel_xpath}//td[text()="{field_label}"]/following-sibling::td[1]').text
|
|
124
|
+
|
|
125
|
+
if exact_match:
|
|
126
|
+
self.assertEqual(value, str(expected_value))
|
|
127
|
+
else:
|
|
128
|
+
self.assertIn(str(expected_value), value)
|
|
58
129
|
|
|
59
130
|
|
|
60
131
|
class BulkOperationsMixin:
|
|
61
132
|
def confirm_bulk_delete_operation(self):
|
|
62
|
-
|
|
133
|
+
"""
|
|
134
|
+
Confirms bulk delete operation on the "warning" page after clicking bulk delete buttons.
|
|
135
|
+
"""
|
|
136
|
+
self.click_button('button[name="_confirm"][type="submit"]')
|
|
63
137
|
|
|
64
138
|
def submit_bulk_edit_operation(self):
|
|
65
|
-
|
|
139
|
+
"""
|
|
140
|
+
Submits the bulk edit form.
|
|
141
|
+
"""
|
|
142
|
+
self.click_button('button[name="_apply"]')
|
|
66
143
|
|
|
67
144
|
def wait_for_job_result(self):
|
|
145
|
+
"""
|
|
146
|
+
Waits 30s for job to be finished.
|
|
147
|
+
"""
|
|
68
148
|
end_statuses = ["Completed", "Failed"]
|
|
69
149
|
WebDriverWait(self.browser, 30).until(
|
|
70
150
|
lambda driver: driver.find_by_id("pending-result-label").text in end_statuses
|
|
@@ -73,16 +153,52 @@ class BulkOperationsMixin:
|
|
|
73
153
|
return self.browser.find_by_id("pending-result-label").text
|
|
74
154
|
|
|
75
155
|
def verify_job_description(self, expected_job_description):
|
|
156
|
+
"""
|
|
157
|
+
Verifies if the job description is correct.
|
|
158
|
+
Waits 30s on page load in case of large payload being sent from bulk edit form.
|
|
159
|
+
"""
|
|
160
|
+
WebDriverWait(self.browser, 30).until(lambda driver: driver.is_text_present("Job Description"))
|
|
161
|
+
|
|
76
162
|
job_description = self.browser.find_by_xpath('//td[text()="Job Description"]/following-sibling::td[1]').text
|
|
77
163
|
self.assertEqual(job_description, expected_job_description)
|
|
78
164
|
|
|
165
|
+
def update_edit_form_value(self, field_name, value, is_select=False):
|
|
166
|
+
"""
|
|
167
|
+
Updates bulk edit form value.
|
|
168
|
+
"""
|
|
169
|
+
if is_select:
|
|
170
|
+
self.fill_select2_field(field_name, value)
|
|
171
|
+
else:
|
|
172
|
+
self.browser.fill(field_name, value)
|
|
173
|
+
|
|
174
|
+
def assertBulkDeleteConfirmMessageIsValid(self, expected_count):
|
|
175
|
+
"""
|
|
176
|
+
Asserts that bulk delete confirmation message is valid and if we're deleting proper number of items.
|
|
177
|
+
"""
|
|
178
|
+
self.browser.is_element_present_by_tag("body", wait_time=30)
|
|
179
|
+
|
|
180
|
+
button_text = self.browser.find_by_xpath('//button[@name="_confirm" and @type="submit"]').text
|
|
181
|
+
self.assertIn(f"Delete these {expected_count}", button_text)
|
|
182
|
+
|
|
183
|
+
message_text = self.browser.find_by_id("confirm-bulk-deletion").find_by_xpath('//div[@class="panel-body"]').text
|
|
184
|
+
self.assertIn(f"The following operation will delete {expected_count}", message_text)
|
|
185
|
+
|
|
79
186
|
def assertIsBulkDeleteJob(self):
|
|
187
|
+
"""
|
|
188
|
+
Asserts if currently visible job is bulk delete job.
|
|
189
|
+
"""
|
|
80
190
|
self.verify_job_description("Bulk delete objects.")
|
|
81
191
|
|
|
82
192
|
def assertIsBulkEditJob(self):
|
|
193
|
+
"""
|
|
194
|
+
Asserts if currently visible job is bulk edit job.
|
|
195
|
+
"""
|
|
83
196
|
self.verify_job_description("Bulk edit objects.")
|
|
84
197
|
|
|
85
198
|
def assertJobStatusIsCompleted(self):
|
|
199
|
+
"""
|
|
200
|
+
Asserts that job was successfully completed.
|
|
201
|
+
"""
|
|
86
202
|
job_status = self.wait_for_job_result()
|
|
87
203
|
self.assertEqual(job_status, "Completed")
|
|
88
204
|
|
|
@@ -100,6 +216,7 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
100
216
|
|
|
101
217
|
host = "0.0.0.0" # noqa: S104 # hardcoded-bind-all-interfaces -- false positive
|
|
102
218
|
selenium_host = SELENIUM_HOST # Docker: `nautobot`; else `host.docker.internal`
|
|
219
|
+
logged_in = False
|
|
103
220
|
|
|
104
221
|
@classmethod
|
|
105
222
|
def setUpClass(cls):
|
|
@@ -123,6 +240,10 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
123
240
|
def live_server_url(cls): # pylint: disable=no-self-argument
|
|
124
241
|
return f"http://{cls.selenium_host}:{cls.server_thread.port}"
|
|
125
242
|
|
|
243
|
+
def tearDown(self):
|
|
244
|
+
if self.logged_in:
|
|
245
|
+
self.logout()
|
|
246
|
+
|
|
126
247
|
@classmethod
|
|
127
248
|
def tearDownClass(cls):
|
|
128
249
|
"""Close down the browser after tests are ran."""
|
|
@@ -195,19 +316,27 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
195
316
|
self.browser.is_element_not_present_by_css(".loading-results", wait_time=5)
|
|
196
317
|
return search_box
|
|
197
318
|
|
|
319
|
+
def _select_select2_result(self):
|
|
320
|
+
found_results = self.browser.find_by_css(".select2-results li.select2-results__option")
|
|
321
|
+
# click the first found item if it's not `None`: special value to nullify field
|
|
322
|
+
if found_results.first.text != "None":
|
|
323
|
+
found_results.first.click()
|
|
324
|
+
else:
|
|
325
|
+
found_results[1].click()
|
|
326
|
+
|
|
198
327
|
def fill_select2_field(self, field_name, value):
|
|
199
328
|
"""
|
|
200
329
|
Helper function to fill a Select2 single selection field on add/edit forms.
|
|
201
330
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
331
|
+
self._fill_select2_field(field_name, value)
|
|
332
|
+
self._select_select2_result()
|
|
204
333
|
|
|
205
334
|
def fill_filters_select2_field(self, field_name, value):
|
|
206
335
|
"""
|
|
207
336
|
Helper function to fill a Select2 single selection field on filters modals.
|
|
208
337
|
"""
|
|
209
338
|
self._fill_select2_field(field_name, value, search_box_class="select2-search select2-search--inline")
|
|
210
|
-
self.
|
|
339
|
+
self._select_select2_result()
|
|
211
340
|
|
|
212
341
|
def fill_select2_multiselect_field(self, field_name, value):
|
|
213
342
|
"""
|
|
@@ -220,3 +349,331 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
220
349
|
# wait for "searching" to disappear
|
|
221
350
|
self.browser.is_element_not_present_by_css(".loading-results", wait_time=5)
|
|
222
351
|
search_box.first.type(Keys.ENTER)
|
|
352
|
+
|
|
353
|
+
def click_button(self, query_selector):
|
|
354
|
+
btn = self.browser.find_by_css(query_selector, wait_time=5)
|
|
355
|
+
# Button might be visible but on the edge and then impossible to click due to vertical/horizontal scrolls
|
|
356
|
+
self.browser.execute_script(f"document.querySelector('{query_selector}').scrollIntoView()")
|
|
357
|
+
btn.click()
|
|
358
|
+
|
|
359
|
+
def login_as_superuser(self):
|
|
360
|
+
self.user.is_superuser = True
|
|
361
|
+
self.user.save()
|
|
362
|
+
self.login(self.user.username, self.password)
|
|
363
|
+
self.logged_in = True
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class BulkOperationsTestCases:
|
|
367
|
+
"""
|
|
368
|
+
Helper classes that runs all the basic bulk-operations test cases like edit/delete with
|
|
369
|
+
filtered / not filtered items along with select all option.
|
|
370
|
+
|
|
371
|
+
To use this class create required items in setUp method:
|
|
372
|
+
- at least four entities in two different groups,
|
|
373
|
+
- provide field for filtering to distinguish between above two groups
|
|
374
|
+
- provide expected counts (if different from default)
|
|
375
|
+
- set edit field and value
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
class BaseTestCase(SeleniumTestCase):
|
|
379
|
+
model_menu_path: tuple[str, str]
|
|
380
|
+
model_base_viewname: str
|
|
381
|
+
model_edit_data: dict[str, Any]
|
|
382
|
+
model_filter_by: dict[str, Any]
|
|
383
|
+
model_class: type[Model]
|
|
384
|
+
override_model_plural: Optional[str] = None
|
|
385
|
+
model_expected_counts: dict[str, int] = {
|
|
386
|
+
"all": 5,
|
|
387
|
+
"filtered": 2,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def model_plural(self) -> str:
|
|
392
|
+
if self.override_model_plural is None:
|
|
393
|
+
return self.model_class._meta.verbose_name_plural
|
|
394
|
+
|
|
395
|
+
return self.override_model_plural
|
|
396
|
+
|
|
397
|
+
def setUp(self):
|
|
398
|
+
super().setUp()
|
|
399
|
+
|
|
400
|
+
self.setup_items()
|
|
401
|
+
self.login_as_superuser()
|
|
402
|
+
self.go_to_model_list_page()
|
|
403
|
+
|
|
404
|
+
def go_to_model_list_page(self):
|
|
405
|
+
self.click_navbar_entry(*self.model_menu_path)
|
|
406
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
407
|
+
|
|
408
|
+
def setup_items(self):
|
|
409
|
+
raise NotImplementedError
|
|
410
|
+
|
|
411
|
+
class BulkEditTestCase(BaseTestCase, ObjectsListMixin, BulkOperationsMixin):
|
|
412
|
+
def test_bulk_edit_require_selection(self):
|
|
413
|
+
# Click "edit selected" without selecting anything
|
|
414
|
+
self.click_bulk_edit()
|
|
415
|
+
|
|
416
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
417
|
+
self.assertTrue(self.browser.is_text_present(f"No {self.model_plural} were selected", wait_time=5))
|
|
418
|
+
|
|
419
|
+
def test_bulk_edit_all_items(self):
|
|
420
|
+
# Select all items and edit them
|
|
421
|
+
self.select_all_items()
|
|
422
|
+
self.click_bulk_edit()
|
|
423
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"))
|
|
424
|
+
|
|
425
|
+
# Edit some data and submit the form
|
|
426
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
427
|
+
self.update_edit_form_value(field_name, field_value)
|
|
428
|
+
self.submit_bulk_edit_operation()
|
|
429
|
+
|
|
430
|
+
# Verify job output
|
|
431
|
+
self.assertIsBulkEditJob()
|
|
432
|
+
self.assertJobStatusIsCompleted()
|
|
433
|
+
|
|
434
|
+
# Assert that data was changed
|
|
435
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
436
|
+
self.assertEqual(found_items, self.model_expected_counts["all"])
|
|
437
|
+
|
|
438
|
+
def test_bulk_edit_one_item(self):
|
|
439
|
+
# Select one filtered item
|
|
440
|
+
self.select_one_item()
|
|
441
|
+
self.click_bulk_edit()
|
|
442
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"))
|
|
443
|
+
|
|
444
|
+
# Edit some data and submit the form
|
|
445
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
446
|
+
self.update_edit_form_value(field_name, field_value)
|
|
447
|
+
self.submit_bulk_edit_operation()
|
|
448
|
+
|
|
449
|
+
# Verify job output
|
|
450
|
+
self.assertIsBulkEditJob()
|
|
451
|
+
self.assertJobStatusIsCompleted()
|
|
452
|
+
|
|
453
|
+
# Assert that data was changed
|
|
454
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
455
|
+
self.assertEqual(found_items, 1)
|
|
456
|
+
|
|
457
|
+
def test_bulk_edit_all_items_from_all_pages(self):
|
|
458
|
+
# Select all from all pages
|
|
459
|
+
self.set_per_page()
|
|
460
|
+
self.select_all_items_from_all_pages()
|
|
461
|
+
self.click_bulk_edit_all()
|
|
462
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
463
|
+
|
|
464
|
+
# Edit some data and submit the form
|
|
465
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
466
|
+
self.update_edit_form_value(field_name, field_value)
|
|
467
|
+
self.submit_bulk_edit_operation()
|
|
468
|
+
|
|
469
|
+
# Verify job output
|
|
470
|
+
self.assertIsBulkEditJob()
|
|
471
|
+
self.assertJobStatusIsCompleted()
|
|
472
|
+
|
|
473
|
+
# Assert that data was changed
|
|
474
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
475
|
+
self.assertEqual(found_items, self.model_expected_counts["all"])
|
|
476
|
+
|
|
477
|
+
def test_bulk_edit_all_filtered_items(self):
|
|
478
|
+
# Filter items
|
|
479
|
+
for field, value in self.model_filter_by.items():
|
|
480
|
+
self.apply_filter(field, value)
|
|
481
|
+
|
|
482
|
+
# Select all filtered items
|
|
483
|
+
self.select_all_items()
|
|
484
|
+
self.click_bulk_edit()
|
|
485
|
+
self.assertIn(
|
|
486
|
+
self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"),
|
|
487
|
+
self.browser.url,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# Edit some data and submit the form
|
|
491
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
492
|
+
self.update_edit_form_value(field_name, field_value)
|
|
493
|
+
self.submit_bulk_edit_operation()
|
|
494
|
+
|
|
495
|
+
# Verify job output
|
|
496
|
+
self.assertIsBulkEditJob()
|
|
497
|
+
self.assertJobStatusIsCompleted()
|
|
498
|
+
|
|
499
|
+
# Assert that data was changed
|
|
500
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
501
|
+
self.assertEqual(found_items, self.model_expected_counts["filtered"])
|
|
502
|
+
|
|
503
|
+
def test_bulk_edit_one_filtered_item(self):
|
|
504
|
+
# Filter items
|
|
505
|
+
for field, value in self.model_filter_by.items():
|
|
506
|
+
self.apply_filter(field, value)
|
|
507
|
+
|
|
508
|
+
# Select one item and edit it
|
|
509
|
+
self.select_one_item()
|
|
510
|
+
self.click_bulk_edit()
|
|
511
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
512
|
+
|
|
513
|
+
# Edit some data and submit the form
|
|
514
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
515
|
+
self.update_edit_form_value(field_name, field_value)
|
|
516
|
+
self.submit_bulk_edit_operation()
|
|
517
|
+
|
|
518
|
+
# Verify job output
|
|
519
|
+
self.assertIsBulkEditJob()
|
|
520
|
+
self.assertJobStatusIsCompleted()
|
|
521
|
+
|
|
522
|
+
# Assert that data was changed
|
|
523
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
524
|
+
self.assertEqual(found_items, 1)
|
|
525
|
+
|
|
526
|
+
def test_bulk_edit_all_filtered_items_from_all_pages(self):
|
|
527
|
+
# Filter items
|
|
528
|
+
self.set_per_page()
|
|
529
|
+
for field, value in self.model_filter_by.items():
|
|
530
|
+
self.apply_filter(field, value)
|
|
531
|
+
|
|
532
|
+
# Select all items and delete them
|
|
533
|
+
self.select_all_items_from_all_pages()
|
|
534
|
+
self.click_bulk_edit_all()
|
|
535
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
536
|
+
|
|
537
|
+
# Edit some data and submit the form
|
|
538
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
539
|
+
self.update_edit_form_value(field_name, field_value)
|
|
540
|
+
self.submit_bulk_edit_operation()
|
|
541
|
+
|
|
542
|
+
# Verify job output
|
|
543
|
+
self.assertIsBulkEditJob()
|
|
544
|
+
self.assertJobStatusIsCompleted()
|
|
545
|
+
|
|
546
|
+
# Assert that data was changed
|
|
547
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
548
|
+
self.assertEqual(found_items, self.model_expected_counts["filtered"])
|
|
549
|
+
|
|
550
|
+
class BulkDeleteTestCase(BaseTestCase, ObjectsListMixin, BulkOperationsMixin):
|
|
551
|
+
def test_bulk_delete_require_selection(self):
|
|
552
|
+
# Click "delete selected" without selecting anything
|
|
553
|
+
self.click_bulk_delete()
|
|
554
|
+
|
|
555
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
556
|
+
self.assertTrue(
|
|
557
|
+
self.browser.is_text_present(f"No {self.model_plural} were selected for deletion.", wait_time=5)
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
def test_bulk_delete_all_items(self):
|
|
561
|
+
# Select all items and delete them
|
|
562
|
+
self.select_all_items()
|
|
563
|
+
self.click_bulk_delete()
|
|
564
|
+
|
|
565
|
+
self.assertEqual(
|
|
566
|
+
self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete")
|
|
567
|
+
)
|
|
568
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["all"])
|
|
569
|
+
self.confirm_bulk_delete_operation()
|
|
570
|
+
|
|
571
|
+
# Verify job output
|
|
572
|
+
self.assertIsBulkDeleteJob()
|
|
573
|
+
self.assertJobStatusIsCompleted()
|
|
574
|
+
|
|
575
|
+
self.go_to_model_list_page()
|
|
576
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
577
|
+
|
|
578
|
+
def test_bulk_delete_one_item(self):
|
|
579
|
+
# Select one item and delete it
|
|
580
|
+
self.select_one_item()
|
|
581
|
+
self.click_bulk_delete()
|
|
582
|
+
|
|
583
|
+
self.assertEqual(
|
|
584
|
+
self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete")
|
|
585
|
+
)
|
|
586
|
+
self.assertBulkDeleteConfirmMessageIsValid(1)
|
|
587
|
+
self.confirm_bulk_delete_operation()
|
|
588
|
+
|
|
589
|
+
# Verify job output
|
|
590
|
+
self.assertIsBulkDeleteJob()
|
|
591
|
+
self.assertJobStatusIsCompleted()
|
|
592
|
+
|
|
593
|
+
self.go_to_model_list_page()
|
|
594
|
+
self.assertEqual(self.objects_list_visible_items, self.model_expected_counts["all"] - 1)
|
|
595
|
+
|
|
596
|
+
def test_bulk_delete_all_items_from_all_pages(self):
|
|
597
|
+
# Select all from all pages
|
|
598
|
+
self.set_per_page()
|
|
599
|
+
self.select_all_items_from_all_pages()
|
|
600
|
+
self.click_bulk_delete_all()
|
|
601
|
+
|
|
602
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
603
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["all"])
|
|
604
|
+
self.confirm_bulk_delete_operation()
|
|
605
|
+
|
|
606
|
+
# Verify job output
|
|
607
|
+
self.assertIsBulkDeleteJob()
|
|
608
|
+
self.assertJobStatusIsCompleted()
|
|
609
|
+
|
|
610
|
+
self.go_to_model_list_page()
|
|
611
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
612
|
+
|
|
613
|
+
def test_bulk_delete_all_filtered_items(self):
|
|
614
|
+
# Filter items
|
|
615
|
+
for field, value in self.model_filter_by.items():
|
|
616
|
+
self.apply_filter(field, value)
|
|
617
|
+
|
|
618
|
+
# Select all items and delete them
|
|
619
|
+
self.select_all_items()
|
|
620
|
+
self.click_bulk_delete()
|
|
621
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
622
|
+
self.confirm_bulk_delete_operation()
|
|
623
|
+
|
|
624
|
+
# Verify job output
|
|
625
|
+
self.assertIsBulkDeleteJob()
|
|
626
|
+
self.assertJobStatusIsCompleted()
|
|
627
|
+
|
|
628
|
+
self.go_to_model_list_page()
|
|
629
|
+
rest_items_count = self.model_expected_counts["all"] - self.model_expected_counts["filtered"]
|
|
630
|
+
self.assertEqual(self.objects_list_visible_items, rest_items_count)
|
|
631
|
+
|
|
632
|
+
def test_bulk_delete_one_filtered_items(self):
|
|
633
|
+
# Filter items
|
|
634
|
+
for field, value in self.model_filter_by.items():
|
|
635
|
+
self.apply_filter(field, value)
|
|
636
|
+
|
|
637
|
+
# Select one item and delete it
|
|
638
|
+
self.select_one_item()
|
|
639
|
+
self.click_bulk_delete()
|
|
640
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
641
|
+
self.confirm_bulk_delete_operation()
|
|
642
|
+
|
|
643
|
+
# Verify job output
|
|
644
|
+
self.assertIsBulkDeleteJob()
|
|
645
|
+
self.assertJobStatusIsCompleted()
|
|
646
|
+
|
|
647
|
+
self.go_to_model_list_page()
|
|
648
|
+
self.assertEqual(self.objects_list_visible_items, self.model_expected_counts["all"] - 1)
|
|
649
|
+
|
|
650
|
+
def test_bulk_delete_all_filtered_items_from_all_pages(self):
|
|
651
|
+
# Filter items
|
|
652
|
+
self.set_per_page()
|
|
653
|
+
for field, value in self.model_filter_by.items():
|
|
654
|
+
self.apply_filter(field, value)
|
|
655
|
+
|
|
656
|
+
# Select all items and delete them
|
|
657
|
+
self.select_all_items_from_all_pages()
|
|
658
|
+
self.click_bulk_delete_all()
|
|
659
|
+
|
|
660
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
661
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["filtered"])
|
|
662
|
+
self.confirm_bulk_delete_operation()
|
|
663
|
+
|
|
664
|
+
# Verify job output
|
|
665
|
+
self.assertIsBulkDeleteJob()
|
|
666
|
+
self.assertJobStatusIsCompleted()
|
|
667
|
+
|
|
668
|
+
self.go_to_model_list_page()
|
|
669
|
+
self.set_per_page(50) # Set page size back to default
|
|
670
|
+
rest_items_count = self.model_expected_counts["all"] - self.model_expected_counts["filtered"]
|
|
671
|
+
self.assertEqual(self.objects_list_visible_items, rest_items_count)
|
|
672
|
+
|
|
673
|
+
# Filter again and assert that all items were deleted
|
|
674
|
+
for field, value in self.model_filter_by.items():
|
|
675
|
+
self.apply_filter(field, value)
|
|
676
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
677
|
+
|
|
678
|
+
class BulkOperationsTestCase(BulkEditTestCase, BulkDeleteTestCase):
|
|
679
|
+
pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
3
|
+
from django.core.management import call_command
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from nautobot.core.testing import TestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ManagementCommandTestCase(TestCase):
|
|
10
|
+
"""Test case for core management commands."""
|
|
11
|
+
|
|
12
|
+
def setUp(self):
|
|
13
|
+
"""Initialize user and client."""
|
|
14
|
+
super().setUpNautobot()
|
|
15
|
+
self.user.is_superuser = True
|
|
16
|
+
self.user.is_staff = True
|
|
17
|
+
self.user.save()
|
|
18
|
+
self.client.force_login(self.user)
|
|
19
|
+
|
|
20
|
+
def test_generate_performance_test_endpoints(self):
|
|
21
|
+
"""Test the generate_performance_test_endpoints management command."""
|
|
22
|
+
out = StringIO()
|
|
23
|
+
call_command("generate_performance_test_endpoints", stdout=out)
|
|
24
|
+
endpoints_dict = yaml.safe_load(out.getvalue())["endpoints"]
|
|
25
|
+
# status_code_to_endpoints = collections.defaultdict(list)
|
|
26
|
+
for view_name, value in endpoints_dict.items():
|
|
27
|
+
for endpoint in value:
|
|
28
|
+
response = self.client.get(endpoint, follow=True)
|
|
29
|
+
self.assertHttpStatus(
|
|
30
|
+
response, 200, f"{view_name}: {endpoint} returns status Code {response.status_code} instead of 200"
|
|
31
|
+
)
|