nautobot 2.2.2__py3-none-any.whl → 2.2.4__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/apps/jobs.py +2 -0
- nautobot/core/api/utils.py +12 -9
- nautobot/core/apps/__init__.py +2 -2
- nautobot/core/celery/__init__.py +79 -68
- nautobot/core/celery/backends.py +9 -1
- nautobot/core/celery/control.py +4 -7
- nautobot/core/celery/schedulers.py +4 -2
- nautobot/core/celery/task.py +78 -5
- nautobot/core/graphql/schema.py +2 -1
- nautobot/core/jobs/__init__.py +2 -1
- nautobot/core/settings.py +6 -4
- nautobot/core/settings.yaml +51 -16
- nautobot/core/templates/admin/base.html +2 -2
- nautobot/core/templates/base_django.html +2 -2
- nautobot/core/templates/buttons/export.html +47 -47
- nautobot/core/templates/generic/object_list.html +3 -3
- nautobot/core/templates/inc/javascript.html +3 -0
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/login.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +2 -0
- nautobot/core/templatetags/helpers.py +66 -9
- nautobot/core/testing/__init__.py +6 -1
- nautobot/core/testing/api.py +12 -13
- nautobot/core/testing/mixins.py +2 -2
- nautobot/core/testing/views.py +50 -51
- nautobot/core/tests/test_api.py +23 -2
- nautobot/core/tests/test_jobs.py +79 -2
- nautobot/core/tests/test_templatetags_helpers.py +32 -0
- nautobot/core/tests/test_views.py +52 -0
- nautobot/core/tests/test_views_utils.py +22 -1
- nautobot/core/utils/module_loading.py +89 -0
- nautobot/core/views/mixins.py +4 -0
- nautobot/core/views/utils.py +3 -2
- nautobot/dcim/choices.py +14 -0
- nautobot/dcim/forms.py +51 -1
- nautobot/dcim/models/device_components.py +9 -5
- nautobot/dcim/templates/dcim/location.html +32 -13
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +102 -0
- nautobot/dcim/tests/test_views.py +376 -55
- nautobot/dcim/urls.py +5 -0
- nautobot/dcim/views.py +172 -21
- nautobot/extras/api/serializers.py +17 -6
- nautobot/extras/api/views.py +21 -10
- nautobot/extras/constants.py +3 -3
- nautobot/extras/datasources/git.py +47 -58
- nautobot/extras/forms/forms.py +3 -1
- nautobot/extras/jobs.py +79 -146
- nautobot/extras/models/datasources.py +0 -2
- nautobot/extras/models/jobs.py +36 -18
- nautobot/extras/plugins/__init__.py +1 -20
- nautobot/extras/signals.py +6 -9
- nautobot/extras/test_jobs/__init__.py +8 -0
- nautobot/extras/test_jobs/dry_run.py +3 -2
- nautobot/extras/test_jobs/fail.py +43 -0
- nautobot/extras/test_jobs/ipaddress_vars.py +40 -1
- nautobot/extras/test_jobs/jobs_module/__init__.py +5 -0
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/__init__.py +1 -0
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +6 -0
- nautobot/extras/test_jobs/pass.py +40 -0
- nautobot/extras/test_jobs/relative_import.py +11 -0
- nautobot/extras/tests/test_api.py +3 -0
- nautobot/extras/tests/test_context_managers.py +18 -0
- nautobot/extras/tests/test_datasources.py +125 -118
- nautobot/extras/tests/test_job_variables.py +57 -15
- nautobot/extras/tests/test_jobs.py +135 -1
- nautobot/extras/tests/test_models.py +26 -19
- nautobot/extras/tests/test_plugins.py +1 -3
- nautobot/extras/tests/test_views.py +2 -4
- nautobot/extras/utils.py +2 -1
- nautobot/extras/views.py +82 -116
- nautobot/ipam/api/views.py +8 -1
- nautobot/ipam/graphql/types.py +11 -0
- nautobot/ipam/mixins.py +32 -0
- nautobot/ipam/models.py +2 -1
- nautobot/ipam/querysets.py +6 -1
- nautobot/ipam/tests/test_models.py +82 -0
- nautobot/ipam/views.py +6 -6
- nautobot/project-static/docs/404.html +107 -51
- nautobot/project-static/docs/apps/index.html +107 -51
- nautobot/project-static/docs/apps/nautobot-apps.html +107 -51
- nautobot/project-static/docs/assets/_mkdocstrings.css +6 -1
- nautobot/project-static/docs/assets/extra.css +11 -0
- nautobot/project-static/docs/assets/javascripts/bundle.3220b9d7.min.js +29 -0
- nautobot/project-static/docs/assets/javascripts/bundle.3220b9d7.min.js.map +7 -0
- nautobot/project-static/docs/assets/stylesheets/main.66ac8b77.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.66ac8b77.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +108 -52
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +287 -262
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +107 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +107 -51
- nautobot/project-static/docs/development/apps/api/configuration-view.html +110 -54
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +110 -54
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +107 -51
- nautobot/project-static/docs/development/apps/api/models/global-search.html +110 -54
- nautobot/project-static/docs/development/apps/api/models/graphql.html +113 -57
- nautobot/project-static/docs/development/apps/api/models/index.html +107 -51
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +113 -57
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +107 -51
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +110 -54
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +107 -51
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +107 -51
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +110 -54
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +111 -55
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +110 -54
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +110 -54
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +107 -51
- nautobot/project-static/docs/development/apps/api/prometheus.html +110 -54
- nautobot/project-static/docs/development/apps/api/setup.html +107 -51
- nautobot/project-static/docs/development/apps/api/testing.html +113 -57
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +110 -54
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +110 -54
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +107 -51
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +107 -51
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +113 -57
- nautobot/project-static/docs/development/apps/api/views/base-template.html +107 -51
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +110 -54
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +107 -51
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +110 -54
- nautobot/project-static/docs/development/apps/api/views/index.html +107 -51
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +113 -57
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +122 -66
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +110 -54
- nautobot/project-static/docs/development/apps/api/views/notes.html +110 -54
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +107 -51
- nautobot/project-static/docs/development/apps/api/views/urls.html +107 -51
- nautobot/project-static/docs/development/apps/index.html +128 -72
- nautobot/project-static/docs/development/apps/migration/code-updates.html +107 -51
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +107 -51
- nautobot/project-static/docs/development/apps/migration/from-v1.html +107 -51
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +107 -51
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +107 -51
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +107 -51
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +107 -51
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +110 -54
- nautobot/project-static/docs/development/core/application-registry.html +242 -144
- nautobot/project-static/docs/development/core/best-practices.html +122 -66
- nautobot/project-static/docs/development/core/bootstrap-ui.html +107 -51
- nautobot/project-static/docs/development/core/caching.html +107 -51
- nautobot/project-static/docs/development/core/controllers.html +107 -51
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +113 -57
- nautobot/project-static/docs/development/core/generic-views.html +110 -54
- nautobot/project-static/docs/development/core/getting-started.html +135 -79
- nautobot/project-static/docs/development/core/homepage.html +110 -54
- nautobot/project-static/docs/development/core/index.html +107 -51
- nautobot/project-static/docs/development/core/model-checklist.html +156 -52
- nautobot/project-static/docs/development/core/model-features.html +108 -52
- nautobot/project-static/docs/development/core/natural-keys.html +110 -54
- nautobot/project-static/docs/development/core/navigation-menu.html +107 -51
- nautobot/project-static/docs/development/core/release-checklist.html +107 -51
- nautobot/project-static/docs/development/core/role-internals.html +107 -51
- nautobot/project-static/docs/development/core/settings.html +107 -51
- nautobot/project-static/docs/development/core/style-guide.html +110 -54
- nautobot/project-static/docs/development/core/templates.html +113 -57
- nautobot/project-static/docs/development/core/testing.html +125 -69
- nautobot/project-static/docs/development/core/user-preferences.html +107 -51
- nautobot/project-static/docs/development/index.html +107 -51
- nautobot/project-static/docs/development/jobs/index.html +504 -172
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +111 -55
- nautobot/project-static/docs/docker/index.html +3 -3
- nautobot/project-static/docs/index.html +125 -69
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +3 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/index.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.0.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.1.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.2.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.3.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.4.html +107 -51
- nautobot/project-static/docs/release-notes/version-1.5.html +116 -60
- nautobot/project-static/docs/release-notes/version-1.6.html +107 -51
- nautobot/project-static/docs/release-notes/version-2.0.html +110 -54
- nautobot/project-static/docs/release-notes/version-2.1.html +107 -51
- nautobot/project-static/docs/release-notes/version-2.2.html +500 -114
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +262 -262
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +107 -51
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +107 -51
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +107 -51
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +107 -51
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +251 -164
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +113 -57
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +113 -57
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +113 -57
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +107 -51
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +107 -51
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +171 -112
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +13 -8626
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +117 -61
- nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +13 -8614
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +252 -165
- nautobot/project-static/docs/user-guide/administration/installation/index.html +165 -192
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +411 -691
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +248 -229
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +13 -8118
- nautobot/project-static/docs/user-guide/administration/installation/services.html +350 -240
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +8684 -0
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +8672 -0
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +8176 -0
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +110 -54
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +110 -54
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +155 -99
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +109 -53
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +107 -51
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +117 -58
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +116 -60
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +119 -63
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +125 -69
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +128 -72
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +227 -60
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +110 -54
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +107 -51
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +113 -57
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +113 -57
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +113 -57
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +110 -54
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +113 -57
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +107 -51
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +110 -54
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +107 -51
- nautobot/project-static/docs/user-guide/index.html +109 -53
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +113 -57
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +128 -72
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +125 -69
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +125 -69
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +143 -100
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +113 -57
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +113 -57
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +123 -67
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +116 -60
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +131 -75
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +149 -93
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +116 -60
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +119 -63
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +137 -81
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +110 -54
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +107 -51
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +110 -54
- nautobot/project-static/js/forms.js +18 -11
- nautobot/tenancy/views.py +2 -6
- nautobot/virtualization/views.py +5 -9
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/METADATA +4 -4
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/RECORD +357 -348
- nautobot/extras/test_jobs/job_variables.py +0 -93
- nautobot/project-static/docs/assets/javascripts/bundle.bd41221c.min.js +0 -29
- nautobot/project-static/docs/assets/javascripts/bundle.bd41221c.min.js.map +0 -7
- nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css.map +0 -1
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/NOTICE +0 -0
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/WHEEL +0 -0
- {nautobot-2.2.2.dist-info → nautobot-2.2.4.dist-info}/entry_points.txt +0 -0
|
@@ -14,8 +14,15 @@ import yaml
|
|
|
14
14
|
from nautobot.circuits.choices import CircuitTerminationSideChoices
|
|
15
15
|
from nautobot.circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
|
16
16
|
from nautobot.core.templatetags.buttons import job_export_url, job_import_url
|
|
17
|
-
from nautobot.core.testing import
|
|
18
|
-
|
|
17
|
+
from nautobot.core.testing import (
|
|
18
|
+
extract_page_body,
|
|
19
|
+
ModelViewTestCase,
|
|
20
|
+
post_data,
|
|
21
|
+
ViewTestCases,
|
|
22
|
+
)
|
|
23
|
+
from nautobot.core.testing.utils import (
|
|
24
|
+
generate_random_device_asset_tag_of_specified_size,
|
|
25
|
+
)
|
|
19
26
|
from nautobot.dcim.choices import (
|
|
20
27
|
CableLengthUnitChoices,
|
|
21
28
|
CableTypeChoices,
|
|
@@ -25,6 +32,7 @@ from nautobot.dcim.choices import (
|
|
|
25
32
|
InterfaceModeChoices,
|
|
26
33
|
InterfaceRedundancyGroupProtocolChoices,
|
|
27
34
|
InterfaceTypeChoices,
|
|
35
|
+
LocationDataToContactActionChoices,
|
|
28
36
|
PortTypeChoices,
|
|
29
37
|
PowerFeedPhaseChoices,
|
|
30
38
|
PowerFeedSupplyChoices,
|
|
@@ -89,10 +97,16 @@ from nautobot.dcim.models import (
|
|
|
89
97
|
SoftwareVersion,
|
|
90
98
|
VirtualChassis,
|
|
91
99
|
)
|
|
92
|
-
from nautobot.dcim.views import
|
|
100
|
+
from nautobot.dcim.views import (
|
|
101
|
+
ConsoleConnectionsListView,
|
|
102
|
+
InterfaceConnectionsListView,
|
|
103
|
+
PowerConnectionsListView,
|
|
104
|
+
)
|
|
93
105
|
from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
|
|
94
106
|
from nautobot.extras.models import (
|
|
95
107
|
ConfigContextSchema,
|
|
108
|
+
Contact,
|
|
109
|
+
ContactAssociation,
|
|
96
110
|
CustomField,
|
|
97
111
|
CustomFieldChoice,
|
|
98
112
|
ExternalIntegration,
|
|
@@ -102,6 +116,7 @@ from nautobot.extras.models import (
|
|
|
102
116
|
SecretsGroup,
|
|
103
117
|
Status,
|
|
104
118
|
Tag,
|
|
119
|
+
Team,
|
|
105
120
|
)
|
|
106
121
|
from nautobot.ipam.choices import IPAddressTypeChoices
|
|
107
122
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup, VRF
|
|
@@ -128,7 +143,11 @@ def create_test_device(name):
|
|
|
128
143
|
devicerole.content_types.add(device_ct)
|
|
129
144
|
devicestatus = Status.objects.get_for_model(Device).first()
|
|
130
145
|
device = Device.objects.create(
|
|
131
|
-
name=name,
|
|
146
|
+
name=name,
|
|
147
|
+
location=location,
|
|
148
|
+
device_type=devicetype,
|
|
149
|
+
role=devicerole,
|
|
150
|
+
status=devicestatus,
|
|
132
151
|
)
|
|
133
152
|
|
|
134
153
|
return device
|
|
@@ -161,7 +180,10 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
161
180
|
"name": "Intermediate 2",
|
|
162
181
|
# "parent": lt1.pk, # TODO: Either overload how EditObjectViewTestCase finds an editable object or write a specific test case for this.
|
|
163
182
|
"description": "Another intermediate type",
|
|
164
|
-
"content_types": [
|
|
183
|
+
"content_types": [
|
|
184
|
+
ContentType.objects.get_for_model(Rack).pk,
|
|
185
|
+
ContentType.objects.get_for_model(Device).pk,
|
|
186
|
+
],
|
|
165
187
|
"nestable": True,
|
|
166
188
|
}
|
|
167
189
|
|
|
@@ -174,6 +196,8 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
174
196
|
|
|
175
197
|
@classmethod
|
|
176
198
|
def setUpTestData(cls):
|
|
199
|
+
cls.contact_statuses = Status.objects.get_for_model(ContactAssociation)
|
|
200
|
+
cls.contact_roles = Role.objects.get_for_model(ContactAssociation)
|
|
177
201
|
lt1 = LocationType.objects.get(name="Campus")
|
|
178
202
|
lt2 = LocationType.objects.get(name="Building")
|
|
179
203
|
lt3 = LocationType.objects.get(name="Floor")
|
|
@@ -186,7 +210,13 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
186
210
|
loc1 = Location.objects.create(name="Root 1", location_type=lt1, status=status)
|
|
187
211
|
loc2 = Location.objects.create(name="Root 2", location_type=lt1, status=status, tenant=tenant)
|
|
188
212
|
loc3 = Location.objects.create(name="Intermediate 1", location_type=lt2, parent=loc2, status=status)
|
|
189
|
-
loc4 = Location.objects.create(
|
|
213
|
+
loc4 = Location.objects.create(
|
|
214
|
+
name="Leaf 1",
|
|
215
|
+
location_type=lt3,
|
|
216
|
+
parent=loc3,
|
|
217
|
+
status=status,
|
|
218
|
+
description="Hi!",
|
|
219
|
+
)
|
|
190
220
|
for loc in [loc1, loc2, loc3, loc4]:
|
|
191
221
|
loc.validated_save()
|
|
192
222
|
|
|
@@ -222,7 +252,9 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
222
252
|
}
|
|
223
253
|
|
|
224
254
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
225
|
-
def test_create_child_location_under_a_non_globally_unique_named_parent_location(
|
|
255
|
+
def test_create_child_location_under_a_non_globally_unique_named_parent_location(
|
|
256
|
+
self,
|
|
257
|
+
):
|
|
226
258
|
self.add_permissions("dcim.add_location")
|
|
227
259
|
status = Status.objects.get_for_model(Location).first()
|
|
228
260
|
region_type = LocationType.objects.create(name="Region")
|
|
@@ -252,6 +284,137 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
252
284
|
self.assertHttpStatus(self.client.post(**request), 302)
|
|
253
285
|
self.assertEqual(Location.objects.get(name="Root 3").parent.pk, site_1.pk)
|
|
254
286
|
|
|
287
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
288
|
+
def test_migrate_location_data_from_location_assign(self):
|
|
289
|
+
self.add_permissions("dcim.change_location")
|
|
290
|
+
location = Location.objects.first()
|
|
291
|
+
location.contact_name = "Should be unique Contact Name"
|
|
292
|
+
location.contact_phone = "123123123"
|
|
293
|
+
location.contact_email = "helloword@example.com"
|
|
294
|
+
location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
|
|
295
|
+
location.shipping_address = "53 blue Locks manchester, NY 12124"
|
|
296
|
+
similar_contact = Contact.objects.first()
|
|
297
|
+
role = self.contact_roles.first().pk
|
|
298
|
+
status = self.contact_statuses.first().pk
|
|
299
|
+
form_data = {
|
|
300
|
+
"action": LocationDataToContactActionChoices.ASSOCIATE_EXISTING_CONTACT,
|
|
301
|
+
"contact": similar_contact.pk,
|
|
302
|
+
"role": role,
|
|
303
|
+
"status": status,
|
|
304
|
+
}
|
|
305
|
+
request = {
|
|
306
|
+
"path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
|
|
307
|
+
"data": post_data(form_data),
|
|
308
|
+
}
|
|
309
|
+
# Assert permission checks are triggered
|
|
310
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
311
|
+
self.add_permissions("extras.add_contactassociation")
|
|
312
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
313
|
+
# assert ContactAssociation is created correctly
|
|
314
|
+
created_contact_association = ContactAssociation.objects.order_by("created").last()
|
|
315
|
+
self.assertEqual(created_contact_association.associated_object_id, location.pk)
|
|
316
|
+
self.assertEqual(created_contact_association.contact.pk, similar_contact.pk)
|
|
317
|
+
self.assertEqual(created_contact_association.role.pk, role)
|
|
318
|
+
self.assertEqual(created_contact_association.status.pk, status)
|
|
319
|
+
|
|
320
|
+
# assert location data is cleared out
|
|
321
|
+
location.refresh_from_db()
|
|
322
|
+
self.assertEqual(location.contact_name, "")
|
|
323
|
+
self.assertEqual(location.contact_phone, "")
|
|
324
|
+
self.assertEqual(location.contact_email, "")
|
|
325
|
+
|
|
326
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
327
|
+
def test_migrate_location_data_from_location_new_contact(self):
|
|
328
|
+
self.add_permissions("dcim.change_location")
|
|
329
|
+
location = Location.objects.first()
|
|
330
|
+
location.contact_name = "Should be unique Contact Name"
|
|
331
|
+
location.contact_phone = "123123123"
|
|
332
|
+
location.contact_email = "helloword@example.com"
|
|
333
|
+
location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
|
|
334
|
+
location.shipping_address = "53 blue Locks manchester, NY 12124"
|
|
335
|
+
role = self.contact_roles.first().pk
|
|
336
|
+
status = self.contact_statuses.first().pk
|
|
337
|
+
form_data = {
|
|
338
|
+
"action": LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_CONTACT,
|
|
339
|
+
"name": "Should be unique Contact Name",
|
|
340
|
+
"phone": "123123123",
|
|
341
|
+
"email": "helloword@example.com",
|
|
342
|
+
"role": role,
|
|
343
|
+
"status": status,
|
|
344
|
+
}
|
|
345
|
+
request = {
|
|
346
|
+
"path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
|
|
347
|
+
"data": post_data(form_data),
|
|
348
|
+
}
|
|
349
|
+
# Assert permission checks are triggered
|
|
350
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
351
|
+
self.add_permissions("extras.add_contactassociation")
|
|
352
|
+
self.add_permissions("extras.add_contact")
|
|
353
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
354
|
+
# assert a new contact is created successfully
|
|
355
|
+
contact = Contact.objects.get(name="Should be unique Contact Name")
|
|
356
|
+
self.assertEqual(contact.name, form_data["name"])
|
|
357
|
+
self.assertEqual(contact.phone, form_data["phone"])
|
|
358
|
+
self.assertEqual(contact.email, form_data["email"])
|
|
359
|
+
# assert ContactAssociation is created correctly
|
|
360
|
+
created_contact_association = ContactAssociation.objects.order_by("created").last()
|
|
361
|
+
self.assertEqual(created_contact_association.associated_object_id, location.pk)
|
|
362
|
+
self.assertEqual(created_contact_association.contact.pk, contact.pk)
|
|
363
|
+
self.assertEqual(created_contact_association.role.pk, role)
|
|
364
|
+
self.assertEqual(created_contact_association.status.pk, status)
|
|
365
|
+
|
|
366
|
+
# assert location data is cleared out
|
|
367
|
+
location.refresh_from_db()
|
|
368
|
+
self.assertEqual(location.contact_name, "")
|
|
369
|
+
self.assertEqual(location.contact_phone, "")
|
|
370
|
+
self.assertEqual(location.contact_email, "")
|
|
371
|
+
|
|
372
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
373
|
+
def test_migrate_location_data_from_location_new_team(self):
|
|
374
|
+
self.add_permissions("dcim.change_location")
|
|
375
|
+
location = Location.objects.first()
|
|
376
|
+
location.contact_name = "Should be unique Team Name"
|
|
377
|
+
location.contact_phone = "123123123"
|
|
378
|
+
location.contact_email = "helloword@example.com"
|
|
379
|
+
location.physical_address = "418 Brown Locks Barrettchester, NM 85792"
|
|
380
|
+
location.shipping_address = "53 blue Locks manchester, NY 12124"
|
|
381
|
+
role = self.contact_roles.first().pk
|
|
382
|
+
status = self.contact_statuses.first().pk
|
|
383
|
+
form_data = {
|
|
384
|
+
"action": LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_TEAM,
|
|
385
|
+
"name": "Should be unique Team Name",
|
|
386
|
+
"phone": "123123123",
|
|
387
|
+
"email": "helloword@example.com",
|
|
388
|
+
"role": role,
|
|
389
|
+
"status": status,
|
|
390
|
+
}
|
|
391
|
+
request = {
|
|
392
|
+
"path": reverse("dcim:location_migrate_data_to_contact", kwargs={"pk": location.pk}),
|
|
393
|
+
"data": post_data(form_data),
|
|
394
|
+
}
|
|
395
|
+
# Assert permission checks are triggered
|
|
396
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
397
|
+
self.add_permissions("extras.add_contactassociation")
|
|
398
|
+
self.add_permissions("extras.add_team")
|
|
399
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
400
|
+
# assert a new team is created successfully
|
|
401
|
+
team = Team.objects.get(name="Should be unique Team Name")
|
|
402
|
+
self.assertEqual(team.name, form_data["name"])
|
|
403
|
+
self.assertEqual(team.phone, form_data["phone"])
|
|
404
|
+
self.assertEqual(team.email, form_data["email"])
|
|
405
|
+
# assert ContactAssociation is created correctly
|
|
406
|
+
created_contact_association = ContactAssociation.objects.order_by("created").last()
|
|
407
|
+
self.assertEqual(created_contact_association.associated_object_id, location.pk)
|
|
408
|
+
self.assertEqual(created_contact_association.team.pk, team.pk)
|
|
409
|
+
self.assertEqual(created_contact_association.role.pk, role)
|
|
410
|
+
self.assertEqual(created_contact_association.status.pk, status)
|
|
411
|
+
|
|
412
|
+
# assert location data is cleared out
|
|
413
|
+
location.refresh_from_db()
|
|
414
|
+
self.assertEqual(location.contact_name, "")
|
|
415
|
+
self.assertEqual(location.contact_phone, "")
|
|
416
|
+
self.assertEqual(location.contact_email, "")
|
|
417
|
+
|
|
255
418
|
|
|
256
419
|
class RackGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
257
420
|
model = RackGroup
|
|
@@ -340,7 +503,11 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
340
503
|
cls.cable_connected = cable_statuses.get(name="Connected")
|
|
341
504
|
|
|
342
505
|
cls.custom_fields = (
|
|
343
|
-
CustomField.objects.create(
|
|
506
|
+
CustomField.objects.create(
|
|
507
|
+
type=CustomFieldTypeChoices.TYPE_MULTISELECT,
|
|
508
|
+
label="Rack Colors",
|
|
509
|
+
default=[],
|
|
510
|
+
),
|
|
344
511
|
)
|
|
345
512
|
|
|
346
513
|
CustomFieldChoice.objects.create(custom_field=cls.custom_fields[0], value="red")
|
|
@@ -389,7 +556,9 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
389
556
|
|
|
390
557
|
for rack in racks:
|
|
391
558
|
RelationshipAssociation(
|
|
392
|
-
relationship=cls.relationships[0],
|
|
559
|
+
relationship=cls.relationships[0],
|
|
560
|
+
source=rack,
|
|
561
|
+
destination=cls.locations[1],
|
|
393
562
|
).validated_save()
|
|
394
563
|
|
|
395
564
|
cls.form_data = {
|
|
@@ -498,19 +667,31 @@ class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
498
667
|
poweroutlet1 = PowerOutlet.objects.create(device=devices[0], name="Power Outlet 11", power_port=powerport1)
|
|
499
668
|
|
|
500
669
|
# connect power port to power feed (single-phase)
|
|
501
|
-
cable1 = Cable(
|
|
670
|
+
cable1 = Cable(
|
|
671
|
+
termination_a=powerfeed1,
|
|
672
|
+
termination_b=powerport1,
|
|
673
|
+
status=self.cable_connected,
|
|
674
|
+
)
|
|
502
675
|
cable1.save()
|
|
503
676
|
|
|
504
677
|
# Create power port for 2nd device
|
|
505
678
|
powerport2 = PowerPort.objects.create(device=devices[1], name="Power Port 12", allocated_draw=1200)
|
|
506
679
|
|
|
507
680
|
# Connect power port to power outlet (dev1)
|
|
508
|
-
cable2 = Cable(
|
|
681
|
+
cable2 = Cable(
|
|
682
|
+
termination_a=powerport2,
|
|
683
|
+
termination_b=poweroutlet1,
|
|
684
|
+
status=self.cable_connected,
|
|
685
|
+
)
|
|
509
686
|
cable2.save()
|
|
510
687
|
|
|
511
688
|
# Create another power port for 2nd device and directly connect to the second PowerFeed.
|
|
512
689
|
powerport3 = PowerPort.objects.create(device=devices[1], name="Power Port 13", allocated_draw=2400)
|
|
513
|
-
cable3 = Cable(
|
|
690
|
+
cable3 = Cable(
|
|
691
|
+
termination_a=powerfeed2,
|
|
692
|
+
termination_b=powerport3,
|
|
693
|
+
status=self.cable_connected,
|
|
694
|
+
)
|
|
514
695
|
cable3.save()
|
|
515
696
|
|
|
516
697
|
# Test the view
|
|
@@ -654,7 +835,10 @@ class DeviceTypeTestCase(
|
|
|
654
835
|
content,
|
|
655
836
|
)
|
|
656
837
|
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
657
|
-
self.assertInHTML(
|
|
838
|
+
self.assertInHTML(
|
|
839
|
+
'<button type="submit" class="btn btn-link" form="export_default">YAML format</button>',
|
|
840
|
+
content,
|
|
841
|
+
)
|
|
658
842
|
self.assertInHTML('<button type="submit" class="btn btn-link">CSV format</button>', content)
|
|
659
843
|
|
|
660
844
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
@@ -1294,7 +1478,12 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1294
1478
|
|
|
1295
1479
|
rack_status = Status.objects.get_for_model(Rack).first()
|
|
1296
1480
|
racks = (
|
|
1297
|
-
Rack.objects.create(
|
|
1481
|
+
Rack.objects.create(
|
|
1482
|
+
name="Rack 1",
|
|
1483
|
+
location=locations[0],
|
|
1484
|
+
rack_group=rack_group,
|
|
1485
|
+
status=rack_status,
|
|
1486
|
+
),
|
|
1298
1487
|
Rack.objects.create(name="Rack 2", location=locations[1], status=rack_status),
|
|
1299
1488
|
)
|
|
1300
1489
|
|
|
@@ -1333,7 +1522,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1333
1522
|
devicetypes[1].software_image_files.set(software_image_files[2:])
|
|
1334
1523
|
|
|
1335
1524
|
cls.custom_fields = (
|
|
1336
|
-
CustomField.objects.create(
|
|
1525
|
+
CustomField.objects.create(
|
|
1526
|
+
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
1527
|
+
label="Crash Counter",
|
|
1528
|
+
default=0,
|
|
1529
|
+
),
|
|
1337
1530
|
)
|
|
1338
1531
|
cls.custom_fields[0].content_types.set([ContentType.objects.get_for_model(Device)])
|
|
1339
1532
|
|
|
@@ -1518,7 +1711,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1518
1711
|
def test_device_interface_assign_ipaddress(self):
|
|
1519
1712
|
device = Device.objects.first()
|
|
1520
1713
|
self.add_permissions(
|
|
1521
|
-
"ipam.add_ipaddress",
|
|
1714
|
+
"ipam.add_ipaddress",
|
|
1715
|
+
"extras.view_status",
|
|
1716
|
+
"ipam.view_namespace",
|
|
1717
|
+
"dcim.view_device",
|
|
1718
|
+
"dcim.view_interface",
|
|
1522
1719
|
)
|
|
1523
1720
|
device_list_url = reverse("dcim:device_interfaces", args=(device.pk,))
|
|
1524
1721
|
namespace = Namespace.objects.first()
|
|
@@ -1554,7 +1751,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1554
1751
|
self.assertHttpStatus(response, 200)
|
|
1555
1752
|
self.interfaces[0].refresh_from_db()
|
|
1556
1753
|
self.assertEqual(self.interfaces[0].ip_addresses.all().count(), 0)
|
|
1557
|
-
self.assertIn(
|
|
1754
|
+
self.assertIn(
|
|
1755
|
+
f"Interface with id "{self.interfaces[0].pk}" not found",
|
|
1756
|
+
response_body,
|
|
1757
|
+
)
|
|
1558
1758
|
|
|
1559
1759
|
with self.subTest("Assert Cannnot assign IPAddress(Exsisting IP) without permission"):
|
|
1560
1760
|
# Assert Assign Exsisting IPAddress
|
|
@@ -1563,7 +1763,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1563
1763
|
self.assertHttpStatus(response, 200)
|
|
1564
1764
|
self.interfaces[1].refresh_from_db()
|
|
1565
1765
|
self.assertEqual(self.interfaces[1].ip_addresses.all().count(), 0)
|
|
1566
|
-
self.assertIn(
|
|
1766
|
+
self.assertIn(
|
|
1767
|
+
f"Interface with id "{self.interfaces[1].pk}" not found",
|
|
1768
|
+
response_body,
|
|
1769
|
+
)
|
|
1567
1770
|
|
|
1568
1771
|
self.add_permissions("dcim.change_interface", "ipam.view_ipaddress")
|
|
1569
1772
|
|
|
@@ -1571,7 +1774,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1571
1774
|
self.assertHttpStatus(self.client.post(**add_new_ip_request), 302)
|
|
1572
1775
|
self.interfaces[0].refresh_from_db()
|
|
1573
1776
|
self.assertEqual(
|
|
1574
|
-
str(self.interfaces[0].ip_addresses.all().first().address),
|
|
1777
|
+
str(self.interfaces[0].ip_addresses.all().first().address),
|
|
1778
|
+
add_new_ip_form_data["address"],
|
|
1575
1779
|
)
|
|
1576
1780
|
|
|
1577
1781
|
with self.subTest("Assert Assign IPAddress"):
|
|
@@ -1681,7 +1885,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1681
1885
|
Assert that the local context passes schema validation via full_clean()
|
|
1682
1886
|
"""
|
|
1683
1887
|
schema = ConfigContextSchema.objects.create(
|
|
1684
|
-
name="Schema 1",
|
|
1888
|
+
name="Schema 1",
|
|
1889
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
1685
1890
|
)
|
|
1686
1891
|
self.add_permissions("dcim.add_device")
|
|
1687
1892
|
|
|
@@ -1695,7 +1900,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1695
1900
|
"data": post_data(form_data),
|
|
1696
1901
|
}
|
|
1697
1902
|
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1698
|
-
self.assertEqual(
|
|
1903
|
+
self.assertEqual(
|
|
1904
|
+
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
1905
|
+
schema.pk,
|
|
1906
|
+
)
|
|
1699
1907
|
|
|
1700
1908
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1701
1909
|
def test_local_config_context_schema_validation_fails(self):
|
|
@@ -1705,7 +1913,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1705
1913
|
Assert that the local context fails schema validation via full_clean()
|
|
1706
1914
|
"""
|
|
1707
1915
|
schema = ConfigContextSchema.objects.create(
|
|
1708
|
-
name="Schema 1",
|
|
1916
|
+
name="Schema 1",
|
|
1917
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
1709
1918
|
)
|
|
1710
1919
|
self.add_permissions("dcim.add_device")
|
|
1711
1920
|
|
|
@@ -1921,10 +2130,16 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
1921
2130
|
Interface.objects.create(device=device, name="Interface 2", status=status_active),
|
|
1922
2131
|
Interface.objects.create(device=device, name="Interface 3", status=status_active),
|
|
1923
2132
|
Interface.objects.create(
|
|
1924
|
-
device=device,
|
|
2133
|
+
device=device,
|
|
2134
|
+
name="LAG",
|
|
2135
|
+
status=status_active,
|
|
2136
|
+
type=InterfaceTypeChoices.TYPE_LAG,
|
|
1925
2137
|
),
|
|
1926
2138
|
Interface.objects.create(
|
|
1927
|
-
device=device,
|
|
2139
|
+
device=device,
|
|
2140
|
+
name="BRIDGE",
|
|
2141
|
+
status=status_active,
|
|
2142
|
+
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
1928
2143
|
),
|
|
1929
2144
|
)
|
|
1930
2145
|
cls.lag_interface = interfaces[3]
|
|
@@ -1936,16 +2151,32 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
1936
2151
|
vlan_group = VLANGroup.objects.first()
|
|
1937
2152
|
vlans = (
|
|
1938
2153
|
VLAN.objects.create(
|
|
1939
|
-
vid=1,
|
|
2154
|
+
vid=1,
|
|
2155
|
+
name="VLAN1",
|
|
2156
|
+
location=device.location,
|
|
2157
|
+
status=vlan_status,
|
|
2158
|
+
vlan_group=vlan_group,
|
|
1940
2159
|
),
|
|
1941
2160
|
VLAN.objects.create(
|
|
1942
|
-
vid=101,
|
|
2161
|
+
vid=101,
|
|
2162
|
+
name="VLAN101",
|
|
2163
|
+
location=device.location,
|
|
2164
|
+
status=vlan_status,
|
|
2165
|
+
vlan_group=vlan_group,
|
|
1943
2166
|
),
|
|
1944
2167
|
VLAN.objects.create(
|
|
1945
|
-
vid=102,
|
|
2168
|
+
vid=102,
|
|
2169
|
+
name="VLAN102",
|
|
2170
|
+
location=device.location,
|
|
2171
|
+
status=vlan_status,
|
|
2172
|
+
vlan_group=vlan_group,
|
|
1946
2173
|
),
|
|
1947
2174
|
VLAN.objects.create(
|
|
1948
|
-
vid=103,
|
|
2175
|
+
vid=103,
|
|
2176
|
+
name="VLAN103",
|
|
2177
|
+
location=device.location,
|
|
2178
|
+
status=vlan_status,
|
|
2179
|
+
vlan_group=vlan_group,
|
|
1949
2180
|
),
|
|
1950
2181
|
)
|
|
1951
2182
|
|
|
@@ -2432,22 +2663,37 @@ class CableTestCase(
|
|
|
2432
2663
|
circuittype = CircuitType.objects.first()
|
|
2433
2664
|
circuit_status = Status.objects.get_for_model(Circuit).first()
|
|
2434
2665
|
circuit = Circuit.objects.create(
|
|
2435
|
-
cid="Circuit 1",
|
|
2666
|
+
cid="Circuit 1",
|
|
2667
|
+
provider=provider,
|
|
2668
|
+
circuit_type=circuittype,
|
|
2669
|
+
status=circuit_status,
|
|
2436
2670
|
)
|
|
2437
2671
|
|
|
2438
2672
|
circuit_terminations = [
|
|
2439
2673
|
CircuitTermination.objects.create(
|
|
2440
|
-
circuit=circuit,
|
|
2674
|
+
circuit=circuit,
|
|
2675
|
+
term_side=CircuitTerminationSideChoices.SIDE_A,
|
|
2676
|
+
location=location,
|
|
2441
2677
|
),
|
|
2442
2678
|
CircuitTermination.objects.create(
|
|
2443
|
-
circuit=circuit,
|
|
2679
|
+
circuit=circuit,
|
|
2680
|
+
term_side=CircuitTerminationSideChoices.SIDE_Z,
|
|
2681
|
+
location=location,
|
|
2444
2682
|
),
|
|
2445
2683
|
]
|
|
2446
2684
|
|
|
2447
2685
|
status = Status.objects.get_for_model(Cable).get(name="Connected")
|
|
2448
2686
|
cables = [
|
|
2449
|
-
Cable.objects.create(
|
|
2450
|
-
|
|
2687
|
+
Cable.objects.create(
|
|
2688
|
+
termination_a=circuit_terminations[0],
|
|
2689
|
+
termination_b=interfaces[0],
|
|
2690
|
+
status=status,
|
|
2691
|
+
),
|
|
2692
|
+
Cable.objects.create(
|
|
2693
|
+
termination_a=circuit_terminations[1],
|
|
2694
|
+
termination_b=interfaces[1],
|
|
2695
|
+
status=status,
|
|
2696
|
+
),
|
|
2451
2697
|
]
|
|
2452
2698
|
|
|
2453
2699
|
request = {
|
|
@@ -2467,7 +2713,10 @@ class CableTestCase(
|
|
|
2467
2713
|
cable_path_1 = CablePath.objects.filter(
|
|
2468
2714
|
Q(origin_type=termination_ct, origin_id=circuit_terminations[0].pk)
|
|
2469
2715
|
| Q(origin_type=interface_ct, origin_id=interfaces[0].pk)
|
|
2470
|
-
| Q(
|
|
2716
|
+
| Q(
|
|
2717
|
+
destination_type=termination_ct,
|
|
2718
|
+
destination_id=circuit_terminations[0].pk,
|
|
2719
|
+
)
|
|
2471
2720
|
| Q(destination_type=interface_ct, destination_id=interfaces[0].pk)
|
|
2472
2721
|
)
|
|
2473
2722
|
# pylint: enable=unsupported-binary-operation
|
|
@@ -2478,7 +2727,10 @@ class CableTestCase(
|
|
|
2478
2727
|
cable_path_2 = CablePath.objects.filter(
|
|
2479
2728
|
Q(origin_type=termination_ct, origin_id=circuit_terminations[1].pk)
|
|
2480
2729
|
| Q(origin_type=interface_ct, origin_id=interfaces[1].pk)
|
|
2481
|
-
| Q(
|
|
2730
|
+
| Q(
|
|
2731
|
+
destination_type=termination_ct,
|
|
2732
|
+
destination_id=circuit_terminations[1].pk,
|
|
2733
|
+
)
|
|
2482
2734
|
| Q(destination_type=interface_ct, destination_id=interfaces[1].pk)
|
|
2483
2735
|
)
|
|
2484
2736
|
# pylint: enable=unsupported-binary-operation
|
|
@@ -2523,9 +2775,21 @@ class ConsoleConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2523
2775
|
)
|
|
2524
2776
|
status_connected = Status.objects.get(name="Connected")
|
|
2525
2777
|
|
|
2526
|
-
Cable.objects.create(
|
|
2527
|
-
|
|
2528
|
-
|
|
2778
|
+
Cable.objects.create(
|
|
2779
|
+
termination_a=consoleports[0],
|
|
2780
|
+
termination_b=serverports[0],
|
|
2781
|
+
status=status_connected,
|
|
2782
|
+
)
|
|
2783
|
+
Cable.objects.create(
|
|
2784
|
+
termination_a=consoleports[1],
|
|
2785
|
+
termination_b=serverports[1],
|
|
2786
|
+
status=status_connected,
|
|
2787
|
+
)
|
|
2788
|
+
Cable.objects.create(
|
|
2789
|
+
termination_a=consoleports[2],
|
|
2790
|
+
termination_b=rearport,
|
|
2791
|
+
status=status_connected,
|
|
2792
|
+
)
|
|
2529
2793
|
|
|
2530
2794
|
|
|
2531
2795
|
class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
@@ -2572,10 +2836,22 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2572
2836
|
|
|
2573
2837
|
status_connected = Status.objects.get(name="Connected")
|
|
2574
2838
|
|
|
2575
|
-
Cable.objects.create(
|
|
2839
|
+
Cable.objects.create(
|
|
2840
|
+
termination_a=powerports[2],
|
|
2841
|
+
termination_b=powerfeed,
|
|
2842
|
+
status=status_connected,
|
|
2843
|
+
)
|
|
2576
2844
|
# Creating a PowerOutlet with a PowerPort via the ORM does *not* automatically cable the two together. Bug?
|
|
2577
|
-
Cable.objects.create(
|
|
2578
|
-
|
|
2845
|
+
Cable.objects.create(
|
|
2846
|
+
termination_a=powerports[0],
|
|
2847
|
+
termination_b=poweroutlets[0],
|
|
2848
|
+
status=status_connected,
|
|
2849
|
+
)
|
|
2850
|
+
Cable.objects.create(
|
|
2851
|
+
termination_a=powerports[1],
|
|
2852
|
+
termination_b=poweroutlets[1],
|
|
2853
|
+
status=status_connected,
|
|
2854
|
+
)
|
|
2579
2855
|
|
|
2580
2856
|
|
|
2581
2857
|
class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
@@ -2608,18 +2884,30 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2608
2884
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
2609
2885
|
cls.interfaces = (
|
|
2610
2886
|
Interface.objects.create(
|
|
2611
|
-
device=device_1,
|
|
2887
|
+
device=device_1,
|
|
2888
|
+
name="Interface 1",
|
|
2889
|
+
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2890
|
+
status=interface_status,
|
|
2612
2891
|
),
|
|
2613
2892
|
Interface.objects.create(
|
|
2614
|
-
device=device_1,
|
|
2893
|
+
device=device_1,
|
|
2894
|
+
name="Interface 2",
|
|
2895
|
+
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2896
|
+
status=interface_status,
|
|
2615
2897
|
),
|
|
2616
2898
|
Interface.objects.create(
|
|
2617
|
-
device=device_1,
|
|
2899
|
+
device=device_1,
|
|
2900
|
+
name="Interface 3",
|
|
2901
|
+
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2902
|
+
status=interface_status,
|
|
2618
2903
|
),
|
|
2619
2904
|
)
|
|
2620
2905
|
|
|
2621
2906
|
cls.device_2_interface = Interface.objects.create(
|
|
2622
|
-
device=device_2,
|
|
2907
|
+
device=device_2,
|
|
2908
|
+
name="Interface 1",
|
|
2909
|
+
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2910
|
+
status=interface_status,
|
|
2623
2911
|
)
|
|
2624
2912
|
rearport = RearPort.objects.create(device=device_2, type=PortTypeChoices.TYPE_8P8C)
|
|
2625
2913
|
|
|
@@ -2627,16 +2915,29 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2627
2915
|
circuittype = CircuitType.objects.first()
|
|
2628
2916
|
circuit_status = Status.objects.get_for_model(Circuit).first()
|
|
2629
2917
|
circuit = Circuit.objects.create(
|
|
2630
|
-
cid="Circuit 1",
|
|
2918
|
+
cid="Circuit 1",
|
|
2919
|
+
provider=provider,
|
|
2920
|
+
circuit_type=circuittype,
|
|
2921
|
+
status=circuit_status,
|
|
2631
2922
|
)
|
|
2632
2923
|
circuittermination = CircuitTermination.objects.create(
|
|
2633
|
-
circuit=circuit,
|
|
2924
|
+
circuit=circuit,
|
|
2925
|
+
term_side=CircuitTerminationSideChoices.SIDE_A,
|
|
2926
|
+
location=location,
|
|
2634
2927
|
)
|
|
2635
2928
|
|
|
2636
2929
|
connected = Status.objects.get(name="Connected")
|
|
2637
2930
|
|
|
2638
|
-
Cable.objects.create(
|
|
2639
|
-
|
|
2931
|
+
Cable.objects.create(
|
|
2932
|
+
termination_a=cls.interfaces[0],
|
|
2933
|
+
termination_b=cls.device_2_interface,
|
|
2934
|
+
status=connected,
|
|
2935
|
+
)
|
|
2936
|
+
Cable.objects.create(
|
|
2937
|
+
termination_a=cls.interfaces[1],
|
|
2938
|
+
termination_b=circuittermination,
|
|
2939
|
+
status=connected,
|
|
2940
|
+
)
|
|
2640
2941
|
Cable.objects.create(termination_a=cls.interfaces[2], termination_b=rearport, status=connected)
|
|
2641
2942
|
|
|
2642
2943
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
@@ -2814,12 +3115,23 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2814
3115
|
status_planned = statuses[0]
|
|
2815
3116
|
|
|
2816
3117
|
powerfeed_1 = PowerFeed.objects.create(
|
|
2817
|
-
name="Power Feed 1",
|
|
3118
|
+
name="Power Feed 1",
|
|
3119
|
+
power_panel=powerpanels[0],
|
|
3120
|
+
rack=racks[0],
|
|
3121
|
+
status=status_planned,
|
|
2818
3122
|
)
|
|
2819
3123
|
powerfeed_2 = PowerFeed.objects.create(
|
|
2820
|
-
name="Power Feed 2",
|
|
3124
|
+
name="Power Feed 2",
|
|
3125
|
+
power_panel=powerpanels[0],
|
|
3126
|
+
rack=racks[0],
|
|
3127
|
+
status=status_planned,
|
|
3128
|
+
)
|
|
3129
|
+
PowerFeed.objects.create(
|
|
3130
|
+
name="Power Feed 3",
|
|
3131
|
+
power_panel=powerpanels[0],
|
|
3132
|
+
rack=racks[0],
|
|
3133
|
+
status=status_planned,
|
|
2821
3134
|
)
|
|
2822
|
-
PowerFeed.objects.create(name="Power Feed 3", power_panel=powerpanels[0], rack=racks[0], status=status_planned)
|
|
2823
3135
|
|
|
2824
3136
|
# Assign power feeds for the tests later
|
|
2825
3137
|
cls.powerfeeds = (powerfeed_1, powerfeed_2)
|
|
@@ -2872,7 +3184,9 @@ class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2872
3184
|
powerfeed = self.powerfeeds[0]
|
|
2873
3185
|
|
|
2874
3186
|
Cable.objects.create(
|
|
2875
|
-
termination_a=powerport,
|
|
3187
|
+
termination_a=powerport,
|
|
3188
|
+
termination_b=powerfeed,
|
|
3189
|
+
status=Status.objects.get(name="Connected"),
|
|
2876
3190
|
)
|
|
2877
3191
|
|
|
2878
3192
|
url = reverse("dcim:powerfeed", kwargs={"pk": powerfeed.pk})
|
|
@@ -2895,7 +3209,11 @@ class PathTraceViewTestCase(ModelViewTestCase):
|
|
|
2895
3209
|
location_type = LocationType.objects.get(name="Campus")
|
|
2896
3210
|
location = Location.objects.create(location_type=location_type, name="Location 1", status=active)
|
|
2897
3211
|
device = Device.objects.create(
|
|
2898
|
-
device_type=devicetype,
|
|
3212
|
+
device_type=devicetype,
|
|
3213
|
+
role=devicerole,
|
|
3214
|
+
name="Device 1",
|
|
3215
|
+
location=location,
|
|
3216
|
+
status=active,
|
|
2899
3217
|
)
|
|
2900
3218
|
obj = RearPort.objects.create(device=device, name="Rear Port 1", type=PortTypeChoices.TYPE_8P8C)
|
|
2901
3219
|
peer_obj = Interface.objects.create(device=device, name="eth0", status=active)
|
|
@@ -3026,7 +3344,10 @@ class InterfaceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3026
3344
|
|
|
3027
3345
|
# Assign unconstrained permission
|
|
3028
3346
|
self.add_permissions("dcim.add_interfaceredundancygroupassociation")
|
|
3029
|
-
return_url = reverse(
|
|
3347
|
+
return_url = reverse(
|
|
3348
|
+
"dcim:interfaceredundancygroup",
|
|
3349
|
+
kwargs={"pk": self.interface_redundancy_groups[0].pk},
|
|
3350
|
+
)
|
|
3030
3351
|
url = reverse("dcim:interfaceredundancygroupassociation_add")
|
|
3031
3352
|
url = url + f"?interface_redundancy_group={self.interface_redundancy_groups[0].pk}&return_url={return_url}"
|
|
3032
3353
|
self.assertHttpStatus(self.client.get(url), 200)
|