nautobot 2.4.1__py3-none-any.whl → 2.4.2__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/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/testing/integration.py +437 -10
- nautobot/core/tests/test_jobs.py +34 -2
- nautobot/core/utils/git.py +7 -2
- nautobot/core/views/generic.py +1 -1
- nautobot/core/views/mixins.py +13 -6
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/forms.py +12 -0
- nautobot/dcim/tables/devices.py +2 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- 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/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 +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- 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/virtualchassis.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- 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 +11 -3
- nautobot/extras/constants.py +1 -0
- nautobot/extras/datasources/git.py +125 -0
- 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_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_models.py +283 -0
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/utils.py +17 -8
- nautobot/extras/views.py +55 -12
- 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/docs/404.html +87 -12
- nautobot/project-static/docs/apps/index.html +87 -12
- nautobot/project-static/docs/apps/nautobot-apps.html +87 -12
- 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 +87 -12
- 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 +87 -12
- 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 +87 -12
- 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 +87 -12
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +87 -12
- nautobot/project-static/docs/development/apps/migration/from-v1.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/development/core/bootstrap-ui.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/development/core/generic-views.html +87 -12
- nautobot/project-static/docs/development/core/getting-started.html +87 -12
- nautobot/project-static/docs/development/core/homepage.html +87 -12
- nautobot/project-static/docs/development/core/index.html +87 -12
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +87 -12
- nautobot/project-static/docs/development/core/model-checklist.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/development/core/release-checklist.html +87 -12
- nautobot/project-static/docs/development/core/role-internals.html +87 -12
- nautobot/project-static/docs/development/core/settings.html +87 -12
- nautobot/project-static/docs/development/core/style-guide.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +87 -12
- nautobot/project-static/docs/index.html +87 -12
- nautobot/project-static/docs/overview/application_stack.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/release-notes/version-1.1.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.2.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.3.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.4.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.5.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.6.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.0.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.1.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.2.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.3.html +87 -12
- nautobot/project-static/docs/release-notes/version-2.4.html +277 -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 +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +90 -15
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +87 -12
- 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 +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/services.html +87 -12
- 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 +9843 -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 +87 -12
- 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 +87 -12
- 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 +87 -12
- 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 +187 -29
- 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 +87 -12
- 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 +87 -12
- 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 +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +87 -12
- 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/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- 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-2.4.1.dist-info → nautobot-2.4.2.dist-info}/METADATA +5 -5
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/RECORD +404 -397
- 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.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from nautobot.circuits.models import Circuit, CircuitType, Provider
|
|
4
|
+
from nautobot.core.testing.integration import (
|
|
5
|
+
BulkOperationsTestCases,
|
|
6
|
+
)
|
|
7
|
+
from nautobot.extras.models import Status
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CircuitBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCase):
|
|
11
|
+
"""
|
|
12
|
+
Test circuits bulk edit / delete operations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
model_menu_path = ("Circuits", "Circuits")
|
|
16
|
+
model_base_viewname = "circuits:circuit"
|
|
17
|
+
model_edit_data = {"commit_rate": "12345"}
|
|
18
|
+
model_filter_by = {"circuit_type": "Copper"}
|
|
19
|
+
model_class = Circuit
|
|
20
|
+
|
|
21
|
+
def setup_items(self):
|
|
22
|
+
Circuit.objects.all().delete()
|
|
23
|
+
|
|
24
|
+
# Create locations for test
|
|
25
|
+
self.create_circuit()
|
|
26
|
+
self.create_circuit()
|
|
27
|
+
self.create_circuit()
|
|
28
|
+
self.create_circuit("Copper")
|
|
29
|
+
self.create_circuit("Copper")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def create_circuit(circuit_type="Fiber"):
|
|
33
|
+
circuit_id = f"TestCircuit-{str(uuid.uuid4())[:6]}"
|
|
34
|
+
provider, _ = Provider.objects.get_or_create(name="A Test Provider")
|
|
35
|
+
circuit_type, _ = CircuitType.objects.get_or_create(name=circuit_type)
|
|
36
|
+
|
|
37
|
+
circuit_status = Status.objects.get_for_model(Circuit).first()
|
|
38
|
+
Circuit.objects.get_or_create(
|
|
39
|
+
cid=circuit_id,
|
|
40
|
+
provider=provider,
|
|
41
|
+
status=circuit_status,
|
|
42
|
+
circuit_type=circuit_type,
|
|
43
|
+
)
|
|
@@ -19,7 +19,7 @@ class CircuitRelationshipsTestCase(SeleniumTestCase):
|
|
|
19
19
|
self.user.is_superuser = True
|
|
20
20
|
self.user.save()
|
|
21
21
|
self.login(self.user.username, self.password)
|
|
22
|
-
location_type = LocationType.objects.
|
|
22
|
+
location_type, _ = LocationType.objects.get_or_create(name="Campus")
|
|
23
23
|
location_ct = ContentType.objects.get_for_model(Location)
|
|
24
24
|
circuit_termination_ct = ContentType.objects.get_for_model(CircuitTermination)
|
|
25
25
|
provider_ct = ContentType.objects.get_for_model(Provider)
|
nautobot/core/apps/__init__.py
CHANGED
|
@@ -367,11 +367,6 @@ class CoreConfig(NautobotConfig):
|
|
|
367
367
|
|
|
368
368
|
super().ready()
|
|
369
369
|
|
|
370
|
-
# Register jobs last after everything else has been done.
|
|
371
|
-
from nautobot.core.celery import import_jobs
|
|
372
|
-
|
|
373
|
-
import_jobs()
|
|
374
|
-
|
|
375
370
|
|
|
376
371
|
class NautobotConstanceConfig(ConstanceConfig):
|
|
377
372
|
"""Override "Constance" app name to "Configuration"."""
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
{% block content %}
|
|
8
8
|
<div class="row">
|
|
9
9
|
<div class="col-md-8 col-md-offset-2">
|
|
10
|
-
<div class="panel panel-danger">
|
|
10
|
+
<div class="panel panel-danger" id="confirm-bulk-deletion">
|
|
11
11
|
<div class="panel-heading"><strong>Confirm Bulk Deletion</strong></div>
|
|
12
12
|
<div class="panel-body">
|
|
13
13
|
<p><strong>Warning:</strong> The following operation will delete {{ total_objs_to_delete }} {{ obj_type_plural }}. {% if not delete_all %}Please carefully review the {{ obj_type_plural }} to be deleted and confirm below.{% endif %}</p>
|
|
@@ -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,68 @@ 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"]')
|
|
44
88
|
|
|
45
89
|
@property
|
|
46
90
|
def objects_list_visible_items(self):
|
|
47
|
-
|
|
91
|
+
"""
|
|
92
|
+
Calculating the visible items. Return 0 if there is no visible items.
|
|
93
|
+
"""
|
|
94
|
+
objects_table_container = self.browser.find_by_xpath('//*[@id="object_list_form"]')
|
|
48
95
|
try:
|
|
49
96
|
objects_table = objects_table_container.find_by_tag("tbody")
|
|
50
97
|
return len(objects_table.find_by_tag("tr"))
|
|
@@ -52,19 +99,32 @@ class ObjectsListMixin:
|
|
|
52
99
|
return 0
|
|
53
100
|
|
|
54
101
|
def apply_filter(self, field, value):
|
|
102
|
+
"""
|
|
103
|
+
Open filter dialog and apply select2 filters.
|
|
104
|
+
You can apply more values to the same filter, by calling this function with same name but different value.
|
|
105
|
+
"""
|
|
55
106
|
self.browser.find_by_xpath('//*[@id="id__filterbtn"]').click()
|
|
56
107
|
self.fill_filters_select2_field(field, value)
|
|
57
|
-
self.
|
|
108
|
+
self.click_button('#default-filter button[type="submit"]')
|
|
58
109
|
|
|
59
110
|
|
|
60
111
|
class BulkOperationsMixin:
|
|
61
112
|
def confirm_bulk_delete_operation(self):
|
|
62
|
-
|
|
113
|
+
"""
|
|
114
|
+
Confirms bulk delete operation on the "warning" page after clicking bulk delete buttons.
|
|
115
|
+
"""
|
|
116
|
+
self.click_button('button[name="_confirm"][type="submit"]')
|
|
63
117
|
|
|
64
118
|
def submit_bulk_edit_operation(self):
|
|
65
|
-
|
|
119
|
+
"""
|
|
120
|
+
Submits the bulk edit form.
|
|
121
|
+
"""
|
|
122
|
+
self.click_button('button[name="_apply"]')
|
|
66
123
|
|
|
67
124
|
def wait_for_job_result(self):
|
|
125
|
+
"""
|
|
126
|
+
Waits 30s for job to be finished.
|
|
127
|
+
"""
|
|
68
128
|
end_statuses = ["Completed", "Failed"]
|
|
69
129
|
WebDriverWait(self.browser, 30).until(
|
|
70
130
|
lambda driver: driver.find_by_id("pending-result-label").text in end_statuses
|
|
@@ -73,16 +133,52 @@ class BulkOperationsMixin:
|
|
|
73
133
|
return self.browser.find_by_id("pending-result-label").text
|
|
74
134
|
|
|
75
135
|
def verify_job_description(self, expected_job_description):
|
|
136
|
+
"""
|
|
137
|
+
Verifies if the job description is correct.
|
|
138
|
+
Waits 30s on page load in case of large payload being sent from bulk edit form.
|
|
139
|
+
"""
|
|
140
|
+
WebDriverWait(self.browser, 30).until(lambda driver: driver.is_text_present("Job Description"))
|
|
141
|
+
|
|
76
142
|
job_description = self.browser.find_by_xpath('//td[text()="Job Description"]/following-sibling::td[1]').text
|
|
77
143
|
self.assertEqual(job_description, expected_job_description)
|
|
78
144
|
|
|
145
|
+
def update_edit_form_value(self, field_name, value, is_select=False):
|
|
146
|
+
"""
|
|
147
|
+
Updates bulk edit form value.
|
|
148
|
+
"""
|
|
149
|
+
if is_select:
|
|
150
|
+
self.fill_select2_field(field_name, value)
|
|
151
|
+
else:
|
|
152
|
+
self.browser.fill(field_name, value)
|
|
153
|
+
|
|
154
|
+
def assertBulkDeleteConfirmMessageIsValid(self, expected_count):
|
|
155
|
+
"""
|
|
156
|
+
Asserts that bulk delete confirmation message is valid and if we're deleting proper number of items.
|
|
157
|
+
"""
|
|
158
|
+
self.browser.is_element_present_by_tag("body", wait_time=30)
|
|
159
|
+
|
|
160
|
+
button_text = self.browser.find_by_xpath('//button[@name="_confirm" and @type="submit"]').text
|
|
161
|
+
self.assertIn(f"Delete these {expected_count}", button_text)
|
|
162
|
+
|
|
163
|
+
message_text = self.browser.find_by_id("confirm-bulk-deletion").find_by_xpath('//div[@class="panel-body"]').text
|
|
164
|
+
self.assertIn(f"The following operation will delete {expected_count}", message_text)
|
|
165
|
+
|
|
79
166
|
def assertIsBulkDeleteJob(self):
|
|
167
|
+
"""
|
|
168
|
+
Asserts if currently visible job is bulk delete job.
|
|
169
|
+
"""
|
|
80
170
|
self.verify_job_description("Bulk delete objects.")
|
|
81
171
|
|
|
82
172
|
def assertIsBulkEditJob(self):
|
|
173
|
+
"""
|
|
174
|
+
Asserts if currently visible job is bulk edit job.
|
|
175
|
+
"""
|
|
83
176
|
self.verify_job_description("Bulk edit objects.")
|
|
84
177
|
|
|
85
178
|
def assertJobStatusIsCompleted(self):
|
|
179
|
+
"""
|
|
180
|
+
Asserts that job was successfully completed.
|
|
181
|
+
"""
|
|
86
182
|
job_status = self.wait_for_job_result()
|
|
87
183
|
self.assertEqual(job_status, "Completed")
|
|
88
184
|
|
|
@@ -207,7 +303,7 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
207
303
|
Helper function to fill a Select2 single selection field on filters modals.
|
|
208
304
|
"""
|
|
209
305
|
self._fill_select2_field(field_name, value, search_box_class="select2-search select2-search--inline")
|
|
210
|
-
self.browser.find_by_xpath(f"//li[@class
|
|
306
|
+
self.browser.find_by_xpath(f"//li[contains(@class, 'select2-results__option') and text()='{value}']").click()
|
|
211
307
|
|
|
212
308
|
def fill_select2_multiselect_field(self, field_name, value):
|
|
213
309
|
"""
|
|
@@ -220,3 +316,334 @@ class SeleniumTestCase(StaticLiveServerTestCase, testing.NautobotTestCaseMixin):
|
|
|
220
316
|
# wait for "searching" to disappear
|
|
221
317
|
self.browser.is_element_not_present_by_css(".loading-results", wait_time=5)
|
|
222
318
|
search_box.first.type(Keys.ENTER)
|
|
319
|
+
|
|
320
|
+
def click_button(self, query_selector):
|
|
321
|
+
btn = self.browser.find_by_css(query_selector, wait_time=5)
|
|
322
|
+
# Button might be visible but on the edge and then impossible to click due to vertical/horizontal scrolls
|
|
323
|
+
self.browser.execute_script(f"document.querySelector('{query_selector}').scrollIntoView()")
|
|
324
|
+
btn.click()
|
|
325
|
+
|
|
326
|
+
def login_as_superuser(self):
|
|
327
|
+
self.user.is_superuser = True
|
|
328
|
+
self.user.save()
|
|
329
|
+
self.login(self.user.username, self.password)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class BulkOperationsTestCases:
|
|
333
|
+
"""
|
|
334
|
+
Helper classes that runs all the basic bulk-operations test cases like edit/delete with
|
|
335
|
+
filtered / not filtered items along with select all option.
|
|
336
|
+
|
|
337
|
+
To use this class create required items in setUp method:
|
|
338
|
+
- at least four entities in two different groups,
|
|
339
|
+
- provide field for filtering to distinguish between above two groups
|
|
340
|
+
- provide expected counts (if different from default)
|
|
341
|
+
- set edit field and value
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
class BaseTestCase(SeleniumTestCase):
|
|
345
|
+
model_menu_path: tuple[str, str]
|
|
346
|
+
model_base_viewname: str
|
|
347
|
+
model_edit_data: dict[str, Any]
|
|
348
|
+
model_filter_by: dict[str, Any]
|
|
349
|
+
model_class: type[Model]
|
|
350
|
+
override_model_plural: Optional[str] = None
|
|
351
|
+
model_expected_counts: dict[str, int] = {
|
|
352
|
+
"all": 5,
|
|
353
|
+
"filtered": 2,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def model_plural(self) -> str:
|
|
358
|
+
if self.override_model_plural is None:
|
|
359
|
+
return self.model_class._meta.verbose_name_plural
|
|
360
|
+
|
|
361
|
+
return self.override_model_plural
|
|
362
|
+
|
|
363
|
+
def setUp(self):
|
|
364
|
+
super().setUp()
|
|
365
|
+
|
|
366
|
+
self.setup_items()
|
|
367
|
+
self.login_as_superuser()
|
|
368
|
+
self.go_to_model_list_page()
|
|
369
|
+
|
|
370
|
+
def tearDown(self):
|
|
371
|
+
self.logout()
|
|
372
|
+
super().tearDown()
|
|
373
|
+
|
|
374
|
+
def go_to_model_list_page(self):
|
|
375
|
+
self.click_navbar_entry(*self.model_menu_path)
|
|
376
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
377
|
+
|
|
378
|
+
def setup_items(self):
|
|
379
|
+
raise NotImplementedError
|
|
380
|
+
|
|
381
|
+
class BulkEditTestCase(BaseTestCase, ObjectsListMixin, BulkOperationsMixin):
|
|
382
|
+
def test_bulk_edit_require_selection(self):
|
|
383
|
+
# Click "edit selected" without selecting anything
|
|
384
|
+
self.click_bulk_edit()
|
|
385
|
+
|
|
386
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
387
|
+
self.assertTrue(self.browser.is_text_present(f"No {self.model_plural} were selected", wait_time=5))
|
|
388
|
+
|
|
389
|
+
def test_bulk_edit_all_items(self):
|
|
390
|
+
# Select all items and edit them
|
|
391
|
+
self.select_all_items()
|
|
392
|
+
self.click_bulk_edit()
|
|
393
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"))
|
|
394
|
+
|
|
395
|
+
# Edit some data and submit the form
|
|
396
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
397
|
+
self.update_edit_form_value(field_name, field_value)
|
|
398
|
+
self.submit_bulk_edit_operation()
|
|
399
|
+
|
|
400
|
+
# Verify job output
|
|
401
|
+
self.assertIsBulkEditJob()
|
|
402
|
+
self.assertJobStatusIsCompleted()
|
|
403
|
+
|
|
404
|
+
# Assert that data was changed
|
|
405
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
406
|
+
self.assertEqual(found_items, self.model_expected_counts["all"])
|
|
407
|
+
|
|
408
|
+
def test_bulk_edit_one_item(self):
|
|
409
|
+
# Select one filtered item
|
|
410
|
+
self.select_one_item()
|
|
411
|
+
self.click_bulk_edit()
|
|
412
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"))
|
|
413
|
+
|
|
414
|
+
# Edit some data and submit the form
|
|
415
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
416
|
+
self.update_edit_form_value(field_name, field_value)
|
|
417
|
+
self.submit_bulk_edit_operation()
|
|
418
|
+
|
|
419
|
+
# Verify job output
|
|
420
|
+
self.assertIsBulkEditJob()
|
|
421
|
+
self.assertJobStatusIsCompleted()
|
|
422
|
+
|
|
423
|
+
# Assert that data was changed
|
|
424
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
425
|
+
self.assertEqual(found_items, 1)
|
|
426
|
+
|
|
427
|
+
def test_bulk_edit_all_items_from_all_pages(self):
|
|
428
|
+
# Select all from all pages
|
|
429
|
+
self.set_per_page()
|
|
430
|
+
self.select_all_items_from_all_pages()
|
|
431
|
+
self.click_bulk_edit_all()
|
|
432
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
433
|
+
|
|
434
|
+
# Edit some data and submit the form
|
|
435
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
436
|
+
self.update_edit_form_value(field_name, field_value)
|
|
437
|
+
self.submit_bulk_edit_operation()
|
|
438
|
+
|
|
439
|
+
# Verify job output
|
|
440
|
+
self.assertIsBulkEditJob()
|
|
441
|
+
self.assertJobStatusIsCompleted()
|
|
442
|
+
|
|
443
|
+
# Assert that data was changed
|
|
444
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
445
|
+
self.assertEqual(found_items, self.model_expected_counts["all"])
|
|
446
|
+
|
|
447
|
+
def test_bulk_edit_all_filtered_items(self):
|
|
448
|
+
# Filter items
|
|
449
|
+
for field, value in self.model_filter_by.items():
|
|
450
|
+
self.apply_filter(field, value)
|
|
451
|
+
|
|
452
|
+
# Select all filtered items
|
|
453
|
+
self.select_all_items()
|
|
454
|
+
self.click_bulk_edit()
|
|
455
|
+
self.assertIn(
|
|
456
|
+
self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"),
|
|
457
|
+
self.browser.url,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Edit some data and submit the form
|
|
461
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
462
|
+
self.update_edit_form_value(field_name, field_value)
|
|
463
|
+
self.submit_bulk_edit_operation()
|
|
464
|
+
|
|
465
|
+
# Verify job output
|
|
466
|
+
self.assertIsBulkEditJob()
|
|
467
|
+
self.assertJobStatusIsCompleted()
|
|
468
|
+
|
|
469
|
+
# Assert that data was changed
|
|
470
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
471
|
+
self.assertEqual(found_items, self.model_expected_counts["filtered"])
|
|
472
|
+
|
|
473
|
+
def test_bulk_edit_one_filtered_item(self):
|
|
474
|
+
# Filter items
|
|
475
|
+
for field, value in self.model_filter_by.items():
|
|
476
|
+
self.apply_filter(field, value)
|
|
477
|
+
|
|
478
|
+
# Select one item and edit it
|
|
479
|
+
self.select_one_item()
|
|
480
|
+
self.click_bulk_edit()
|
|
481
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
482
|
+
|
|
483
|
+
# Edit some data and submit the form
|
|
484
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
485
|
+
self.update_edit_form_value(field_name, field_value)
|
|
486
|
+
self.submit_bulk_edit_operation()
|
|
487
|
+
|
|
488
|
+
# Verify job output
|
|
489
|
+
self.assertIsBulkEditJob()
|
|
490
|
+
self.assertJobStatusIsCompleted()
|
|
491
|
+
|
|
492
|
+
# Assert that data was changed
|
|
493
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
494
|
+
self.assertEqual(found_items, 1)
|
|
495
|
+
|
|
496
|
+
def test_bulk_edit_all_filtered_items_from_all_pages(self):
|
|
497
|
+
# Filter items
|
|
498
|
+
self.set_per_page()
|
|
499
|
+
for field, value in self.model_filter_by.items():
|
|
500
|
+
self.apply_filter(field, value)
|
|
501
|
+
|
|
502
|
+
# Select all items and delete them
|
|
503
|
+
self.select_all_items_from_all_pages()
|
|
504
|
+
self.click_bulk_edit_all()
|
|
505
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_edit"), self.browser.url)
|
|
506
|
+
|
|
507
|
+
# Edit some data and submit the form
|
|
508
|
+
for field_name, field_value in self.model_edit_data.items():
|
|
509
|
+
self.update_edit_form_value(field_name, field_value)
|
|
510
|
+
self.submit_bulk_edit_operation()
|
|
511
|
+
|
|
512
|
+
# Verify job output
|
|
513
|
+
self.assertIsBulkEditJob()
|
|
514
|
+
self.assertJobStatusIsCompleted()
|
|
515
|
+
|
|
516
|
+
# Assert that data was changed
|
|
517
|
+
found_items = self.model_class.objects.filter(**self.model_edit_data).count()
|
|
518
|
+
self.assertEqual(found_items, self.model_expected_counts["filtered"])
|
|
519
|
+
|
|
520
|
+
class BulkDeleteTestCase(BaseTestCase, ObjectsListMixin, BulkOperationsMixin):
|
|
521
|
+
def test_bulk_delete_require_selection(self):
|
|
522
|
+
# Click "delete selected" without selecting anything
|
|
523
|
+
self.click_bulk_delete()
|
|
524
|
+
|
|
525
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_list"))
|
|
526
|
+
self.assertTrue(
|
|
527
|
+
self.browser.is_text_present(f"No {self.model_plural} were selected for deletion.", wait_time=5)
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
def test_bulk_delete_all_items(self):
|
|
531
|
+
# Select all items and delete them
|
|
532
|
+
self.select_all_items()
|
|
533
|
+
self.click_bulk_delete()
|
|
534
|
+
|
|
535
|
+
self.assertEqual(
|
|
536
|
+
self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete")
|
|
537
|
+
)
|
|
538
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["all"])
|
|
539
|
+
self.confirm_bulk_delete_operation()
|
|
540
|
+
|
|
541
|
+
# Verify job output
|
|
542
|
+
self.assertIsBulkDeleteJob()
|
|
543
|
+
self.assertJobStatusIsCompleted()
|
|
544
|
+
|
|
545
|
+
self.go_to_model_list_page()
|
|
546
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
547
|
+
|
|
548
|
+
def test_bulk_delete_one_item(self):
|
|
549
|
+
# Select one item and delete it
|
|
550
|
+
self.select_one_item()
|
|
551
|
+
self.click_bulk_delete()
|
|
552
|
+
|
|
553
|
+
self.assertEqual(
|
|
554
|
+
self.browser.url, self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete")
|
|
555
|
+
)
|
|
556
|
+
self.assertBulkDeleteConfirmMessageIsValid(1)
|
|
557
|
+
self.confirm_bulk_delete_operation()
|
|
558
|
+
|
|
559
|
+
# Verify job output
|
|
560
|
+
self.assertIsBulkDeleteJob()
|
|
561
|
+
self.assertJobStatusIsCompleted()
|
|
562
|
+
|
|
563
|
+
self.go_to_model_list_page()
|
|
564
|
+
self.assertEqual(self.objects_list_visible_items, self.model_expected_counts["all"] - 1)
|
|
565
|
+
|
|
566
|
+
def test_bulk_delete_all_items_from_all_pages(self):
|
|
567
|
+
# Select all from all pages
|
|
568
|
+
self.set_per_page()
|
|
569
|
+
self.select_all_items_from_all_pages()
|
|
570
|
+
self.click_bulk_delete_all()
|
|
571
|
+
|
|
572
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
573
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["all"])
|
|
574
|
+
self.confirm_bulk_delete_operation()
|
|
575
|
+
|
|
576
|
+
# Verify job output
|
|
577
|
+
self.assertIsBulkDeleteJob()
|
|
578
|
+
self.assertJobStatusIsCompleted()
|
|
579
|
+
|
|
580
|
+
self.go_to_model_list_page()
|
|
581
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
582
|
+
|
|
583
|
+
def test_bulk_delete_all_filtered_items(self):
|
|
584
|
+
# Filter items
|
|
585
|
+
for field, value in self.model_filter_by.items():
|
|
586
|
+
self.apply_filter(field, value)
|
|
587
|
+
|
|
588
|
+
# Select all items and delete them
|
|
589
|
+
self.select_all_items()
|
|
590
|
+
self.click_bulk_delete()
|
|
591
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
592
|
+
self.confirm_bulk_delete_operation()
|
|
593
|
+
|
|
594
|
+
# Verify job output
|
|
595
|
+
self.assertIsBulkDeleteJob()
|
|
596
|
+
self.assertJobStatusIsCompleted()
|
|
597
|
+
|
|
598
|
+
self.go_to_model_list_page()
|
|
599
|
+
rest_items_count = self.model_expected_counts["all"] - self.model_expected_counts["filtered"]
|
|
600
|
+
self.assertEqual(self.objects_list_visible_items, rest_items_count)
|
|
601
|
+
|
|
602
|
+
def test_bulk_delete_one_filtered_items(self):
|
|
603
|
+
# Filter items
|
|
604
|
+
for field, value in self.model_filter_by.items():
|
|
605
|
+
self.apply_filter(field, value)
|
|
606
|
+
|
|
607
|
+
# Select one item and delete it
|
|
608
|
+
self.select_one_item()
|
|
609
|
+
self.click_bulk_delete()
|
|
610
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
611
|
+
self.confirm_bulk_delete_operation()
|
|
612
|
+
|
|
613
|
+
# Verify job output
|
|
614
|
+
self.assertIsBulkDeleteJob()
|
|
615
|
+
self.assertJobStatusIsCompleted()
|
|
616
|
+
|
|
617
|
+
self.go_to_model_list_page()
|
|
618
|
+
self.assertEqual(self.objects_list_visible_items, self.model_expected_counts["all"] - 1)
|
|
619
|
+
|
|
620
|
+
def test_bulk_delete_all_filtered_items_from_all_pages(self):
|
|
621
|
+
# Filter items
|
|
622
|
+
self.set_per_page()
|
|
623
|
+
for field, value in self.model_filter_by.items():
|
|
624
|
+
self.apply_filter(field, value)
|
|
625
|
+
|
|
626
|
+
# Select all items and delete them
|
|
627
|
+
self.select_all_items_from_all_pages()
|
|
628
|
+
self.click_bulk_delete_all()
|
|
629
|
+
|
|
630
|
+
self.assertIn(self.live_server_url + reverse(f"{self.model_base_viewname}_bulk_delete"), self.browser.url)
|
|
631
|
+
self.assertBulkDeleteConfirmMessageIsValid(self.model_expected_counts["filtered"])
|
|
632
|
+
self.confirm_bulk_delete_operation()
|
|
633
|
+
|
|
634
|
+
# Verify job output
|
|
635
|
+
self.assertIsBulkDeleteJob()
|
|
636
|
+
self.assertJobStatusIsCompleted()
|
|
637
|
+
|
|
638
|
+
self.go_to_model_list_page()
|
|
639
|
+
self.set_per_page(50) # Set page size back to default
|
|
640
|
+
rest_items_count = self.model_expected_counts["all"] - self.model_expected_counts["filtered"]
|
|
641
|
+
self.assertEqual(self.objects_list_visible_items, rest_items_count)
|
|
642
|
+
|
|
643
|
+
# Filter again and assert that all items were deleted
|
|
644
|
+
for field, value in self.model_filter_by.items():
|
|
645
|
+
self.apply_filter(field, value)
|
|
646
|
+
self.assertEqual(self.objects_list_visible_items, 0)
|
|
647
|
+
|
|
648
|
+
class BulkOperationsTestCase(BulkEditTestCase, BulkDeleteTestCase):
|
|
649
|
+
pass
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -750,16 +750,48 @@ class BulkEditTestCase(TransactionTestCase):
|
|
|
750
750
|
description="Example description for bulk edit",
|
|
751
751
|
)
|
|
752
752
|
|
|
753
|
-
# Assert Namespaces
|
|
753
|
+
# Assert Namespaces within pk_list get updated tags
|
|
754
754
|
for namespace in namespaces[:3]:
|
|
755
755
|
self.assertTrue(namespace.tags.filter(pk__in=[tag.pk for tag in self.tags[:3]]).exists())
|
|
756
756
|
self.assertFalse(namespace.tags.filter(pk__in=[tag.pk for tag in self.tags[3:]]).exists())
|
|
757
757
|
|
|
758
|
-
# Assert Namespaces not
|
|
758
|
+
# Assert Namespaces not within pk_list did not get updated tags
|
|
759
759
|
for namespace in namespaces[3:]:
|
|
760
760
|
self.assertFalse(namespace.tags.filter(pk__in=[tag.pk for tag in self.tags[:3]]).exists())
|
|
761
761
|
self.assertTrue(namespace.tags.filter(pk__in=[tag.pk for tag in self.tags[3:]]).exists())
|
|
762
762
|
|
|
763
|
+
job_result = create_job_result_and_run_job(
|
|
764
|
+
"nautobot.core.jobs.bulk_actions",
|
|
765
|
+
"BulkEditObjects",
|
|
766
|
+
content_type=self.namespace_ct.id,
|
|
767
|
+
edit_all=False,
|
|
768
|
+
filter_query_params={},
|
|
769
|
+
form_data={
|
|
770
|
+
"pk": pk_list,
|
|
771
|
+
"description": "Example description for bulk edit",
|
|
772
|
+
"add_tags": [str(self.tags[0].id)],
|
|
773
|
+
"remove_tags": [str(self.tags[-1].id)],
|
|
774
|
+
},
|
|
775
|
+
username=self.user.username,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
self._common_no_error_test_assertion(
|
|
779
|
+
Namespace,
|
|
780
|
+
job_result,
|
|
781
|
+
3,
|
|
782
|
+
description="Example description for bulk edit",
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
# Assert Namespaces within pk_list get updated tag
|
|
786
|
+
for namespace in namespaces[:3]:
|
|
787
|
+
self.assertTrue(namespace.tags.filter(pk__in=[self.tags[0].pk]).exists())
|
|
788
|
+
self.assertFalse(namespace.tags.filter(pk__in=[self.tags[-1].pk]).exists())
|
|
789
|
+
|
|
790
|
+
# Assert Namespaces not within pk_list did not get updated tag
|
|
791
|
+
for namespace in namespaces[3:]:
|
|
792
|
+
self.assertFalse(namespace.tags.filter(pk__in=[self.tags[0].pk]).exists())
|
|
793
|
+
self.assertTrue(namespace.tags.filter(pk__in=[self.tags[-1].pk]).exists())
|
|
794
|
+
|
|
763
795
|
def test_bulk_edit_objects_filter_all(self):
|
|
764
796
|
"""
|
|
765
797
|
Bulk edit all of the filtered Status instances.
|
nautobot/core/utils/git.py
CHANGED
|
@@ -57,7 +57,7 @@ class BranchDoesNotExist(Exception):
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class GitRepo:
|
|
60
|
-
def __init__(self, path, url, clone_initially=True):
|
|
60
|
+
def __init__(self, path, url, clone_initially=True, branch=None, depth=0):
|
|
61
61
|
"""
|
|
62
62
|
Ensure that we have a clone of the given remote Git repository URL at the given local directory path.
|
|
63
63
|
|
|
@@ -65,6 +65,8 @@ class GitRepo:
|
|
|
65
65
|
path (str): path to git repo
|
|
66
66
|
url (str): git repo url
|
|
67
67
|
clone_initially (bool): True if the repo needs to be cloned
|
|
68
|
+
branch (str): branch to checkout
|
|
69
|
+
depth (int): depth of the clone
|
|
68
70
|
"""
|
|
69
71
|
self.url = url
|
|
70
72
|
self.sanitized_url = sanitize(url)
|
|
@@ -73,7 +75,10 @@ class GitRepo:
|
|
|
73
75
|
elif clone_initially:
|
|
74
76
|
# Don't log `url` as it may include authentication details.
|
|
75
77
|
logger.debug("Cloning git repository to %s...", path)
|
|
76
|
-
|
|
78
|
+
if not depth:
|
|
79
|
+
self.repo = Repo.clone_from(url, to_path=path, env=GIT_ENVIRONMENT, branch=branch)
|
|
80
|
+
else:
|
|
81
|
+
self.repo = Repo.clone_from(url, to_path=path, env=GIT_ENVIRONMENT, branch=branch, depth=depth)
|
|
77
82
|
else:
|
|
78
83
|
self.repo = Repo.init(path)
|
|
79
84
|
self.repo.create_remote("origin", url=url)
|