nautobot 2.4.14__py3-none-any.whl → 2.4.15__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/choices.py +8 -0
- nautobot/apps/ui.py +14 -0
- nautobot/core/api/views.py +2 -0
- nautobot/core/choices.py +4 -0
- nautobot/core/filters.py +21 -41
- nautobot/core/management/commands/check_job_approval_status.py +47 -0
- nautobot/core/management/commands/generate_test_data.py +1 -1
- nautobot/core/management/commands/migrate.py +1 -1
- nautobot/core/models/tree_queries.py +17 -0
- nautobot/core/settings.py +2 -2
- nautobot/core/tables.py +25 -2
- nautobot/core/templates/base_django.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/generic/object_list.html +17 -20
- nautobot/core/templates/inc/breadcrumbs.html +14 -0
- nautobot/core/templatetags/buttons.py +2 -4
- nautobot/core/templatetags/helpers.py +29 -6
- nautobot/core/templatetags/ui_framework.py +21 -0
- nautobot/core/testing/filters.py +20 -3
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/tests/integration/test_filters.py +2 -2
- nautobot/core/tests/test_breadcrumbs.py +366 -0
- nautobot/core/tests/test_commands.py +40 -0
- nautobot/core/tests/test_filters.py +51 -1
- nautobot/core/tests/test_forms.py +1 -1
- nautobot/core/tests/test_graphql.py +4 -4
- nautobot/core/tests/test_titles.py +183 -0
- nautobot/core/tests/test_tree_queries.py +30 -0
- nautobot/core/tests/test_views.py +2 -2
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +538 -0
- nautobot/core/ui/bulk_buttons.py +53 -0
- nautobot/core/ui/object_detail.py +31 -8
- nautobot/core/ui/titles.py +127 -0
- nautobot/core/ui/utils.py +25 -0
- nautobot/core/utils/migrations.py +1 -1
- nautobot/core/views/__init__.py +1 -1
- nautobot/core/views/mixins.py +26 -1
- nautobot/core/views/renderers.py +20 -2
- nautobot/core/views/utils.py +13 -12
- nautobot/dcim/api/serializers.py +9 -0
- nautobot/dcim/choices.py +53 -0
- nautobot/dcim/filters/__init__.py +15 -3
- nautobot/dcim/forms.py +120 -7
- nautobot/dcim/management/commands/trace_paths.py +1 -1
- nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
- nautobot/dcim/models/device_component_templates.py +8 -0
- nautobot/dcim/models/device_components.py +31 -12
- nautobot/dcim/models/devices.py +1 -1
- nautobot/dcim/models/power.py +171 -10
- nautobot/dcim/models/racks.py +7 -4
- nautobot/dcim/tables/devices.py +2 -0
- nautobot/dcim/tables/devicetypes.py +1 -0
- nautobot/dcim/tables/power.py +30 -2
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
- nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
- nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
- nautobot/dcim/tests/test_api.py +24 -4
- nautobot/dcim/tests/test_filters.py +91 -13
- nautobot/dcim/tests/test_models.py +262 -0
- nautobot/dcim/tests/test_views.py +20 -12
- nautobot/dcim/utils.py +9 -0
- nautobot/dcim/views.py +390 -77
- nautobot/extras/factory.py +19 -20
- nautobot/extras/filters/__init__.py +3 -2
- nautobot/extras/filters/mixins.py +15 -1
- nautobot/extras/forms/__init__.py +2 -1
- nautobot/extras/forms/forms.py +62 -0
- nautobot/extras/managers.py +4 -1
- nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
- nautobot/extras/models/customfields.py +1 -2
- nautobot/extras/models/datasources.py +1 -2
- nautobot/extras/models/jobs.py +7 -3
- nautobot/extras/plugins/views.py +24 -1
- nautobot/extras/secrets/__init__.py +1 -1
- nautobot/extras/tables.py +9 -0
- nautobot/extras/templates/extras/customfield.html +2 -129
- nautobot/extras/templates/extras/customfield_edit.html +2 -108
- nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
- nautobot/extras/templates/extras/customfield_update.html +108 -0
- nautobot/extras/templates/extras/inc/jobresult.html +7 -3
- nautobot/extras/templates/extras/jobresult.html +2 -155
- nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
- nautobot/extras/templates/extras/marketplace.html +5 -6
- nautobot/extras/templates/extras/note.html +2 -53
- nautobot/extras/templates/extras/note_retrieve.html +53 -0
- nautobot/extras/templates/extras/plugins_list.html +5 -6
- nautobot/extras/templatetags/custom_links.py +2 -2
- nautobot/extras/templatetags/job_buttons.py +1 -1
- nautobot/extras/templatetags/plugins.py +1 -1
- nautobot/extras/tests/integration/test_computedfields.py +2 -2
- nautobot/extras/tests/integration/test_customfields.py +14 -11
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_notes.py +1 -1
- nautobot/extras/tests/integration/test_plugins.py +6 -6
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_filters.py +9 -0
- nautobot/extras/tests/test_forms.py +2 -2
- nautobot/extras/tests/test_plugins.py +2 -3
- nautobot/extras/tests/test_relationships.py +7 -7
- nautobot/extras/tests/test_views.py +172 -1
- nautobot/extras/urls.py +3 -59
- nautobot/extras/utils.py +1 -1
- nautobot/extras/views.py +77 -178
- nautobot/ipam/tables.py +8 -15
- nautobot/ipam/tests/migration/test_migrations.py +8 -8
- nautobot/ipam/tests/test_api.py +2 -2
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/project-static/docs/404.html +23 -0
- nautobot/project-static/docs/apps/index.html +23 -0
- nautobot/project-static/docs/apps/nautobot-apps.html +23 -0
- nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +28 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +128 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +37 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +39 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +32 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +41 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +39 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +43 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +74 -59
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +143 -28
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +43 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +135 -53
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +229 -36
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +27 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +162 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +258 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2620
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +154 -55
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +150 -35
- nautobot/project-static/docs/development/apps/api/configuration-view.html +23 -0
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/global-search.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/graphql.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +23 -0
- nautobot/project-static/docs/development/apps/api/prometheus.html +23 -0
- nautobot/project-static/docs/development/apps/api/setup.html +23 -0
- nautobot/project-static/docs/development/apps/api/testing.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/base-template.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/notes.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/urls.html +23 -0
- nautobot/project-static/docs/development/apps/index.html +23 -0
- nautobot/project-static/docs/development/apps/migration/code-updates.html +23 -0
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +23 -0
- nautobot/project-static/docs/development/apps/migration/from-v1.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +26 -3
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +23 -0
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +26 -3
- nautobot/project-static/docs/development/core/application-registry.html +23 -0
- nautobot/project-static/docs/development/core/best-practices.html +23 -0
- nautobot/project-static/docs/development/core/bootstrap-ui.html +23 -0
- nautobot/project-static/docs/development/core/caching.html +23 -0
- nautobot/project-static/docs/development/core/controllers.html +23 -0
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +23 -0
- nautobot/project-static/docs/development/core/generic-views.html +23 -0
- nautobot/project-static/docs/development/core/getting-started.html +23 -0
- nautobot/project-static/docs/development/core/homepage.html +23 -0
- nautobot/project-static/docs/development/core/index.html +23 -0
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +23 -0
- nautobot/project-static/docs/development/core/model-checklist.html +23 -0
- nautobot/project-static/docs/development/core/model-features.html +23 -0
- nautobot/project-static/docs/development/core/natural-keys.html +23 -0
- nautobot/project-static/docs/development/core/navigation-menu.html +23 -0
- nautobot/project-static/docs/development/core/release-checklist.html +23 -0
- nautobot/project-static/docs/development/core/role-internals.html +23 -0
- nautobot/project-static/docs/development/core/settings.html +23 -0
- nautobot/project-static/docs/development/core/style-guide.html +23 -0
- nautobot/project-static/docs/development/core/templates.html +23 -0
- nautobot/project-static/docs/development/core/testing.html +23 -0
- nautobot/project-static/docs/development/core/ui-component-framework.html +713 -255
- nautobot/project-static/docs/development/core/user-preferences.html +23 -0
- nautobot/project-static/docs/development/index.html +23 -0
- nautobot/project-static/docs/development/jobs/getting-started.html +23 -0
- nautobot/project-static/docs/development/jobs/index.html +23 -0
- nautobot/project-static/docs/development/jobs/installation.html +23 -0
- nautobot/project-static/docs/development/jobs/job-extensions.html +23 -0
- nautobot/project-static/docs/development/jobs/job-logging.html +23 -0
- nautobot/project-static/docs/development/jobs/job-patterns.html +23 -0
- nautobot/project-static/docs/development/jobs/job-structure.html +23 -0
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -0
- nautobot/project-static/docs/development/jobs/testing.html +23 -0
- nautobot/project-static/docs/index.html +23 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
- nautobot/project-static/docs/media/power_distribution.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +23 -0
- nautobot/project-static/docs/overview/design_philosophy.html +23 -0
- nautobot/project-static/docs/release-notes/index.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.0.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.1.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.2.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.3.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.4.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.5.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.6.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.0.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.1.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.2.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.3.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.4.html +231 -0
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +303 -299
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/services.html +23 -0
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +23 -0
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +23 -0
- nautobot/project-static/docs/user-guide/administration/security/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +23 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +284 -219
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +305 -5
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +136 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +41 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +40 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +23 -0
- nautobot/project-static/docs/user-guide/index.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +23 -0
- nautobot/users/tests/test_api.py +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
- nautobot/virtualization/urls.py +3 -61
- nautobot/virtualization/views.py +48 -72
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/METADATA +24 -24
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/RECORD +433 -416
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/NOTICE +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/WHEEL +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.15.dist-info}/entry_points.txt +0 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
|
|
3
|
+
from django.urls import reverse
|
|
4
|
+
from selenium.webdriver.common.by import By
|
|
5
|
+
from selenium.webdriver.support import expected_conditions
|
|
6
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
7
|
+
|
|
3
8
|
from nautobot.core.testing.integration import (
|
|
4
9
|
BulkOperationsTestCases,
|
|
10
|
+
SeleniumTestCase,
|
|
5
11
|
)
|
|
6
12
|
from nautobot.dcim.models import Device
|
|
7
13
|
from nautobot.extras.tests.integration import create_test_device
|
|
@@ -28,3 +34,58 @@ class DeviceBulkOperationsTestCase(BulkOperationsTestCases.BulkOperationsTestCas
|
|
|
28
34
|
create_test_device("Test Device Integration Test 3", test_uuid=test_uuid)
|
|
29
35
|
create_test_device("Test Device Integration Test 4", "Test Location 2", test_uuid=test_uuid)
|
|
30
36
|
create_test_device("Test Device Integration Test 5", "Test Location 2", test_uuid=test_uuid)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DeviceBulkUrlParamTestCase(SeleniumTestCase):
|
|
40
|
+
"""
|
|
41
|
+
Integration test to check that when a bulk edit is initiated from a filtered device list view it does not fill in bulk edit form.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def setUp(self):
|
|
45
|
+
super().setUp()
|
|
46
|
+
self.user.is_superuser = True
|
|
47
|
+
self.user.save()
|
|
48
|
+
self.login(self.user.username, self.password)
|
|
49
|
+
|
|
50
|
+
self.device1 = create_test_device("Device 1")
|
|
51
|
+
|
|
52
|
+
def tearDown(self):
|
|
53
|
+
self.logout()
|
|
54
|
+
super().tearDown()
|
|
55
|
+
|
|
56
|
+
def test_param_fills_device_type(self):
|
|
57
|
+
"""
|
|
58
|
+
This test:
|
|
59
|
+
1 Go to device list page with param for device_type
|
|
60
|
+
2 Selects the row checkbox for the device with that device_type
|
|
61
|
+
3 Submits the bulk edit form
|
|
62
|
+
4 Checks that the device_type field is blank (i.e. "---------") on the bulk edit form
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# Go to Device list page
|
|
66
|
+
self.browser.visit(
|
|
67
|
+
self.live_server_url + reverse("dcim:device_list") + f"?device_type={self.device1.device_type.pk}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 2) Check the row checkbox exactly matching the PK
|
|
71
|
+
pk = str(self.device1.pk)
|
|
72
|
+
cb_xpath = f'//input[@type="checkbox" and @name="pk" and @value="{pk}"]'
|
|
73
|
+
checkbox = WebDriverWait(self.browser.driver, 2).until(
|
|
74
|
+
expected_conditions.element_to_be_clickable((By.XPATH, cb_xpath))
|
|
75
|
+
)
|
|
76
|
+
self.browser.driver.execute_script("arguments[0].click();", checkbox)
|
|
77
|
+
|
|
78
|
+
# Click the bulk-edit button (it uses formaction)
|
|
79
|
+
bulk_url = reverse("dcim:device_bulk_edit")
|
|
80
|
+
btn_xpath = f'//button[@type="submit" and @formaction="{bulk_url}"]'
|
|
81
|
+
bulk_btn = WebDriverWait(self.browser.driver, 2).until(
|
|
82
|
+
expected_conditions.element_to_be_clickable((By.XPATH, btn_xpath))
|
|
83
|
+
)
|
|
84
|
+
# We know this works since if nothing is selected, you will be redirected back to the list view with a message
|
|
85
|
+
bulk_btn.click()
|
|
86
|
+
|
|
87
|
+
self.assertTrue(
|
|
88
|
+
WebDriverWait(self.browser.driver, 2).until(
|
|
89
|
+
lambda d: d.find_element(By.CLASS_NAME, "select2-selection__placeholder").text.strip() == "---------"
|
|
90
|
+
)
|
|
91
|
+
)
|
nautobot/dcim/tests/test_api.py
CHANGED
|
@@ -16,8 +16,10 @@ from nautobot.dcim.choices import (
|
|
|
16
16
|
InterfaceModeChoices,
|
|
17
17
|
InterfaceTypeChoices,
|
|
18
18
|
PortTypeChoices,
|
|
19
|
+
PowerFeedBreakerPoleChoices,
|
|
19
20
|
PowerFeedTypeChoices,
|
|
20
21
|
PowerOutletTypeChoices,
|
|
22
|
+
PowerPanelTypeChoices,
|
|
21
23
|
PowerPortTypeChoices,
|
|
22
24
|
SoftwareImageFileHashingAlgorithmChoices,
|
|
23
25
|
SubdeviceRoleChoices,
|
|
@@ -3064,7 +3066,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|
|
3064
3066
|
# Interface name starts with parent device's position in VC; e.g. 1/1, 1/2, 1/3...
|
|
3065
3067
|
Interface.objects.create(
|
|
3066
3068
|
device=device,
|
|
3067
|
-
name=f"{i%3+1}/{j}",
|
|
3069
|
+
name=f"{i % 3 + 1}/{j}",
|
|
3068
3070
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
3069
3071
|
status=interface_status,
|
|
3070
3072
|
role=interface_role,
|
|
@@ -3166,6 +3168,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|
|
3166
3168
|
|
|
3167
3169
|
class PowerPanelTest(APIViewTestCases.APIViewTestCase):
|
|
3168
3170
|
model = PowerPanel
|
|
3171
|
+
choices_fields = ["panel_type", "power_path"]
|
|
3169
3172
|
|
|
3170
3173
|
@classmethod
|
|
3171
3174
|
def setUpTestData(cls):
|
|
@@ -3204,7 +3207,7 @@ class PowerPanelTest(APIViewTestCases.APIViewTestCase):
|
|
|
3204
3207
|
|
|
3205
3208
|
class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
|
3206
3209
|
model = PowerFeed
|
|
3207
|
-
choices_fields = ["phase", "supply", "type"]
|
|
3210
|
+
choices_fields = ["phase", "supply", "type", "breaker_pole_count", "power_path"]
|
|
3208
3211
|
|
|
3209
3212
|
@classmethod
|
|
3210
3213
|
def setUpTestData(cls):
|
|
@@ -3229,8 +3232,20 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
|
|
3229
3232
|
)
|
|
3230
3233
|
|
|
3231
3234
|
power_panels = (
|
|
3232
|
-
PowerPanel.objects.create(
|
|
3233
|
-
|
|
3235
|
+
PowerPanel.objects.create(
|
|
3236
|
+
location=location,
|
|
3237
|
+
rack_group=rackgroup,
|
|
3238
|
+
name="Power Panel 1",
|
|
3239
|
+
panel_type=PowerPanelTypeChoices.TYPE_UTILITY,
|
|
3240
|
+
breaker_position_count=42,
|
|
3241
|
+
),
|
|
3242
|
+
PowerPanel.objects.create(
|
|
3243
|
+
location=location,
|
|
3244
|
+
rack_group=rackgroup,
|
|
3245
|
+
name="Power Panel 2",
|
|
3246
|
+
panel_type=PowerPanelTypeChoices.TYPE_RPP,
|
|
3247
|
+
breaker_position_count=24,
|
|
3248
|
+
),
|
|
3234
3249
|
)
|
|
3235
3250
|
|
|
3236
3251
|
PRIMARY = PowerFeedTypeChoices.TYPE_PRIMARY
|
|
@@ -3285,6 +3300,9 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
|
|
3285
3300
|
{
|
|
3286
3301
|
"name": "Power Feed 4A",
|
|
3287
3302
|
"power_panel": power_panels[0].pk,
|
|
3303
|
+
"destination_panel": power_panels[1].pk,
|
|
3304
|
+
"breaker_position": 5,
|
|
3305
|
+
"breaker_pole_count": PowerFeedBreakerPoleChoices.POLE_1,
|
|
3288
3306
|
"rack": racks[3].pk,
|
|
3289
3307
|
"status": statuses[0].pk,
|
|
3290
3308
|
"type": PRIMARY,
|
|
@@ -3292,6 +3310,8 @@ class PowerFeedTest(APIViewTestCases.APIViewTestCase):
|
|
|
3292
3310
|
{
|
|
3293
3311
|
"name": "Power Feed 4B",
|
|
3294
3312
|
"power_panel": power_panels[1].pk,
|
|
3313
|
+
"breaker_position": 10,
|
|
3314
|
+
"breaker_pole_count": PowerFeedBreakerPoleChoices.POLE_2,
|
|
3295
3315
|
"rack": racks[3].pk,
|
|
3296
3316
|
"status": statuses[0].pk,
|
|
3297
3317
|
"type": REDUNDANT,
|
|
@@ -14,10 +14,12 @@ from nautobot.dcim.choices import (
|
|
|
14
14
|
InterfaceModeChoices,
|
|
15
15
|
InterfaceTypeChoices,
|
|
16
16
|
PortTypeChoices,
|
|
17
|
+
PowerFeedBreakerPoleChoices,
|
|
17
18
|
PowerFeedPhaseChoices,
|
|
18
19
|
PowerFeedSupplyChoices,
|
|
19
20
|
PowerFeedTypeChoices,
|
|
20
21
|
PowerOutletFeedLegChoices,
|
|
22
|
+
PowerPathChoices,
|
|
21
23
|
RackDimensionUnitChoices,
|
|
22
24
|
RackTypeChoices,
|
|
23
25
|
RackWidthChoices,
|
|
@@ -120,6 +122,7 @@ from nautobot.dcim.models import (
|
|
|
120
122
|
VirtualChassis,
|
|
121
123
|
VirtualDeviceContext,
|
|
122
124
|
)
|
|
125
|
+
from nautobot.extras.filters.mixins import RoleFilter, StatusFilter
|
|
123
126
|
from nautobot.extras.models import ExternalIntegration, Role, SecretsGroup, Status, Tag
|
|
124
127
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix, Service, VLAN, VLANGroup
|
|
125
128
|
from nautobot.tenancy.models import Tenant
|
|
@@ -230,6 +233,7 @@ def common_test_data(cls):
|
|
|
230
233
|
PowerPanel.objects.create(name="Power Panel 1", location=loc0, rack_group=rack_groups[0]),
|
|
231
234
|
PowerPanel.objects.create(name="Power Panel 2", location=loc1, rack_group=rack_groups[1]),
|
|
232
235
|
PowerPanel.objects.create(name="Power Panel 3", location=loc1, rack_group=rack_groups[2]),
|
|
236
|
+
PowerPanel.objects.create(name="Power Panel 4", location=loc0),
|
|
233
237
|
)
|
|
234
238
|
power_panels[0].tags.set(Tag.objects.get_for_model(PowerPanel))
|
|
235
239
|
power_panels[1].tags.set(Tag.objects.get_for_model(PowerPanel)[:3])
|
|
@@ -350,7 +354,19 @@ def common_test_data(cls):
|
|
|
350
354
|
power_feeds = (
|
|
351
355
|
PowerFeed.objects.create(name="Power Feed 1", rack=racks[0], power_panel=power_panels[0], status=pf_status),
|
|
352
356
|
PowerFeed.objects.create(name="Power Feed 2", rack=racks[1], power_panel=power_panels[1], status=pf_status),
|
|
353
|
-
PowerFeed.objects.create(
|
|
357
|
+
PowerFeed.objects.create(
|
|
358
|
+
name="Power Feed 3",
|
|
359
|
+
rack=racks[2],
|
|
360
|
+
power_panel=power_panels[2],
|
|
361
|
+
status=pf_status,
|
|
362
|
+
destination_panel=power_panels[0],
|
|
363
|
+
),
|
|
364
|
+
PowerFeed.objects.create(
|
|
365
|
+
name="Power Feed 4",
|
|
366
|
+
power_panel=power_panels[1],
|
|
367
|
+
status=pf_status,
|
|
368
|
+
destination_panel=power_panels[3],
|
|
369
|
+
),
|
|
354
370
|
)
|
|
355
371
|
power_feeds[0].tags.set(Tag.objects.get_for_model(PowerFeed))
|
|
356
372
|
power_feeds[1].tags.set(Tag.objects.get_for_model(PowerFeed)[:3])
|
|
@@ -696,42 +712,42 @@ def common_test_data(cls):
|
|
|
696
712
|
# Create 3 of each component template on the first two module types
|
|
697
713
|
for i in range(6):
|
|
698
714
|
ConsolePortTemplate.objects.create(
|
|
699
|
-
name=f"Test Filters Module Console Port {i+1}",
|
|
715
|
+
name=f"Test Filters Module Console Port {i + 1}",
|
|
700
716
|
module_type=module_types[i % 2],
|
|
701
717
|
)
|
|
702
718
|
ConsoleServerPortTemplate.objects.create(
|
|
703
|
-
name=f"Test Filters Module Console Server Port {i+1}",
|
|
719
|
+
name=f"Test Filters Module Console Server Port {i + 1}",
|
|
704
720
|
module_type=module_types[i % 2],
|
|
705
721
|
)
|
|
706
722
|
ppt = PowerPortTemplate.objects.create(
|
|
707
|
-
name=f"Test Filters Module Power Port {i+1}",
|
|
723
|
+
name=f"Test Filters Module Power Port {i + 1}",
|
|
708
724
|
module_type=module_types[i % 2],
|
|
709
725
|
)
|
|
710
726
|
PowerOutletTemplate.objects.create(
|
|
711
|
-
name=f"Test Filters Module Power Outlet {i+1}",
|
|
727
|
+
name=f"Test Filters Module Power Outlet {i + 1}",
|
|
712
728
|
power_port_template=ppt,
|
|
713
729
|
module_type=module_types[i % 2],
|
|
714
730
|
)
|
|
715
731
|
InterfaceTemplate.objects.create(
|
|
716
|
-
name=f"Test Filters Module Interface {i+1}",
|
|
732
|
+
name=f"Test Filters Module Interface {i + 1}",
|
|
717
733
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
718
734
|
module_type=module_types[i % 2],
|
|
719
735
|
)
|
|
720
736
|
rpt = RearPortTemplate.objects.create(
|
|
721
|
-
name=f"Test Filters Module Rear Port {i+1}",
|
|
737
|
+
name=f"Test Filters Module Rear Port {i + 1}",
|
|
722
738
|
module_type=module_types[i % 2],
|
|
723
739
|
type=PortTypeChoices.TYPE_8P8C,
|
|
724
740
|
positions=10,
|
|
725
741
|
)
|
|
726
742
|
FrontPortTemplate.objects.create(
|
|
727
|
-
name=f"Test Filters Module Front Port {i+1}",
|
|
743
|
+
name=f"Test Filters Module Front Port {i + 1}",
|
|
728
744
|
module_type=module_types[i % 2],
|
|
729
745
|
rear_port_template=rpt,
|
|
730
746
|
rear_port_position=i + 1,
|
|
731
747
|
type=PortTypeChoices.TYPE_8P8C,
|
|
732
748
|
)
|
|
733
749
|
ModuleBayTemplate.objects.create(
|
|
734
|
-
name=f"Test Filters Module Module Bay {i+1}",
|
|
750
|
+
name=f"Test Filters Module Module Bay {i + 1}",
|
|
735
751
|
position=i + 1,
|
|
736
752
|
module_type=module_types[i % 2],
|
|
737
753
|
requires_first_party_modules=(i % 2 == 0), # True for even indices, False for odd
|
|
@@ -1253,7 +1269,7 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
|
|
|
1253
1269
|
|
|
1254
1270
|
rack_group = RackGroup.objects.get(name="Rack Group 3")
|
|
1255
1271
|
tenant = Tenant.objects.filter(tenant_group__isnull=False).first()
|
|
1256
|
-
rack_role = Role.objects.get_for_model(Rack).first()
|
|
1272
|
+
cls.rack_role = Role.objects.get_for_model(Rack).first()
|
|
1257
1273
|
|
|
1258
1274
|
Rack.objects.create(
|
|
1259
1275
|
name="Rack 4",
|
|
@@ -1262,7 +1278,7 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
|
|
|
1262
1278
|
rack_group=rack_group,
|
|
1263
1279
|
tenant=tenant,
|
|
1264
1280
|
status=cls.rack_statuses[0],
|
|
1265
|
-
role=rack_role,
|
|
1281
|
+
role=cls.rack_role,
|
|
1266
1282
|
serial="ABCDEF",
|
|
1267
1283
|
asset_tag="1004",
|
|
1268
1284
|
type=RackTypeChoices.TYPE_2POST,
|
|
@@ -1291,6 +1307,34 @@ class RackTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilter
|
|
|
1291
1307
|
params = {"outer_unit": [RackDimensionUnitChoices.UNIT_MILLIMETER]}
|
|
1292
1308
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1293
1309
|
|
|
1310
|
+
def test_role_status_negation(self):
|
|
1311
|
+
"""https://github.com/nautobot/nautobot/issues/6456"""
|
|
1312
|
+
self.assertIsInstance(self.filterset().filters["role"], RoleFilter)
|
|
1313
|
+
self.assertIsInstance(self.filterset().filters["role__n"], RoleFilter)
|
|
1314
|
+
with self.subTest("Negated role (id)"):
|
|
1315
|
+
params = {"role__n": [self.rack_role.pk]}
|
|
1316
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1317
|
+
self.filterset(params, self.queryset).qs, Rack.objects.exclude(role=self.rack_role)
|
|
1318
|
+
)
|
|
1319
|
+
with self.subTest("Negated role (name)"):
|
|
1320
|
+
params = {"role__n": [self.rack_role.name]}
|
|
1321
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1322
|
+
self.filterset(params, self.queryset).qs, Rack.objects.exclude(role=self.rack_role)
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
self.assertIsInstance(self.filterset().filters["status"], StatusFilter)
|
|
1326
|
+
self.assertIsInstance(self.filterset().filters["status__n"], StatusFilter)
|
|
1327
|
+
with self.subTest("Negated status (id)"):
|
|
1328
|
+
params = {"status__n": [self.rack_statuses[0].pk]}
|
|
1329
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1330
|
+
self.filterset(params, self.queryset).qs, Rack.objects.exclude(status=self.rack_statuses[0])
|
|
1331
|
+
)
|
|
1332
|
+
with self.subTest("Negated status (name)"):
|
|
1333
|
+
params = {"status__n": [self.rack_statuses[0].name]}
|
|
1334
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1335
|
+
self.filterset(params, self.queryset).qs, Rack.objects.exclude(status=self.rack_statuses[0])
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1294
1338
|
|
|
1295
1339
|
class RackReservationTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
|
|
1296
1340
|
queryset = RackReservation.objects.all()
|
|
@@ -3559,8 +3603,6 @@ class PowerPanelTestCase(FilterTestCases.FilterTestCase):
|
|
|
3559
3603
|
def setUpTestData(cls):
|
|
3560
3604
|
common_test_data(cls)
|
|
3561
3605
|
|
|
3562
|
-
PowerPanel.objects.create(name="Power Panel 4", location=cls.loc1)
|
|
3563
|
-
|
|
3564
3606
|
|
|
3565
3607
|
class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCase):
|
|
3566
3608
|
queryset = PowerFeed.objects.all()
|
|
@@ -3568,8 +3610,12 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3568
3610
|
generic_filter_tests = [
|
|
3569
3611
|
("amperage",),
|
|
3570
3612
|
("available_power",),
|
|
3613
|
+
("breaker_pole_count",),
|
|
3614
|
+
("breaker_position",),
|
|
3571
3615
|
("cable", "cable__id"),
|
|
3572
3616
|
("comments",),
|
|
3617
|
+
("destination_panel", "destination_panel__id"),
|
|
3618
|
+
("destination_panel", "destination_panel__name"),
|
|
3573
3619
|
("max_utilization",),
|
|
3574
3620
|
("name",),
|
|
3575
3621
|
("power_panel", "power_panel__id"),
|
|
@@ -3589,6 +3635,7 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3589
3635
|
PowerFeed.objects.get(name="Power Feed 1"),
|
|
3590
3636
|
PowerFeed.objects.get(name="Power Feed 2"),
|
|
3591
3637
|
PowerFeed.objects.get(name="Power Feed 3"),
|
|
3638
|
+
PowerFeed.objects.get(name="Power Feed 4"),
|
|
3592
3639
|
)
|
|
3593
3640
|
|
|
3594
3641
|
pf_statuses = Status.objects.get_for_model(PowerFeed)
|
|
@@ -3602,6 +3649,9 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3602
3649
|
amperage=100,
|
|
3603
3650
|
max_utilization=10,
|
|
3604
3651
|
comments="PFA",
|
|
3652
|
+
power_path=PowerPathChoices.PATH_A,
|
|
3653
|
+
breaker_position=1,
|
|
3654
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
|
|
3605
3655
|
)
|
|
3606
3656
|
PowerFeed.objects.filter(pk=power_feeds[1].pk).update(
|
|
3607
3657
|
status=pf_statuses[1],
|
|
@@ -3612,6 +3662,9 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3612
3662
|
amperage=200,
|
|
3613
3663
|
max_utilization=20,
|
|
3614
3664
|
comments="PFB",
|
|
3665
|
+
power_path=PowerPathChoices.PATH_B,
|
|
3666
|
+
breaker_position=4,
|
|
3667
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
|
|
3615
3668
|
)
|
|
3616
3669
|
PowerFeed.objects.filter(pk=power_feeds[2].pk).update(
|
|
3617
3670
|
status=pf_statuses[2],
|
|
@@ -3622,14 +3675,32 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3622
3675
|
amperage=300,
|
|
3623
3676
|
max_utilization=30,
|
|
3624
3677
|
comments="PFC",
|
|
3678
|
+
power_path=PowerPathChoices.PATH_A,
|
|
3679
|
+
breaker_position=9,
|
|
3680
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_3,
|
|
3681
|
+
)
|
|
3682
|
+
PowerFeed.objects.filter(pk=power_feeds[3].pk).update(
|
|
3683
|
+
status=pf_statuses[0],
|
|
3684
|
+
type=PowerFeedTypeChoices.TYPE_REDUNDANT,
|
|
3685
|
+
supply=PowerFeedSupplyChoices.SUPPLY_AC,
|
|
3686
|
+
phase=PowerFeedPhaseChoices.PHASE_3PHASE,
|
|
3687
|
+
voltage=400,
|
|
3688
|
+
amperage=400,
|
|
3689
|
+
max_utilization=40,
|
|
3690
|
+
comments="PFD",
|
|
3691
|
+
power_path=PowerPathChoices.PATH_B,
|
|
3692
|
+
breaker_position=15,
|
|
3693
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
|
|
3625
3694
|
)
|
|
3626
3695
|
|
|
3627
3696
|
power_feeds[0].refresh_from_db()
|
|
3628
3697
|
power_feeds[1].refresh_from_db()
|
|
3629
3698
|
power_feeds[2].refresh_from_db()
|
|
3699
|
+
power_feeds[3].refresh_from_db()
|
|
3630
3700
|
power_feeds[0].validated_save()
|
|
3631
3701
|
power_feeds[1].validated_save()
|
|
3632
3702
|
power_feeds[2].validated_save()
|
|
3703
|
+
power_feeds[3].validated_save()
|
|
3633
3704
|
|
|
3634
3705
|
power_ports = (
|
|
3635
3706
|
PowerPort.objects.get(name="Power Port 1"),
|
|
@@ -3674,6 +3745,13 @@ class PowerFeedTestCase(PathEndpointModelTestMixin, FilterTestCases.FilterTestCa
|
|
|
3674
3745
|
self.queryset.filter(phase=PowerFeedPhaseChoices.PHASE_3PHASE),
|
|
3675
3746
|
)
|
|
3676
3747
|
|
|
3748
|
+
def test_power_path(self):
|
|
3749
|
+
params = {"power_path": [PowerPathChoices.PATH_A]}
|
|
3750
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
3751
|
+
self.filterset(params, self.queryset).qs,
|
|
3752
|
+
self.queryset.filter(power_path=PowerPathChoices.PATH_A),
|
|
3753
|
+
)
|
|
3754
|
+
|
|
3677
3755
|
|
|
3678
3756
|
class DeviceRedundancyGroupTestCase(FilterTestCases.FilterTestCase):
|
|
3679
3757
|
queryset = DeviceRedundancyGroup.objects.all()
|
|
@@ -19,6 +19,10 @@ from nautobot.dcim.choices import (
|
|
|
19
19
|
InterfaceModeChoices,
|
|
20
20
|
InterfaceTypeChoices,
|
|
21
21
|
PortTypeChoices,
|
|
22
|
+
PowerFeedBreakerPoleChoices,
|
|
23
|
+
PowerFeedPhaseChoices,
|
|
24
|
+
PowerFeedSupplyChoices,
|
|
25
|
+
PowerFeedTypeChoices,
|
|
22
26
|
PowerOutletFeedLegChoices,
|
|
23
27
|
PowerOutletTypeChoices,
|
|
24
28
|
PowerPortTypeChoices,
|
|
@@ -53,6 +57,7 @@ from nautobot.dcim.models import (
|
|
|
53
57
|
ModuleFamily,
|
|
54
58
|
ModuleType,
|
|
55
59
|
Platform,
|
|
60
|
+
PowerFeed,
|
|
56
61
|
PowerOutlet,
|
|
57
62
|
PowerOutletTemplate,
|
|
58
63
|
PowerPanel,
|
|
@@ -2389,6 +2394,263 @@ class CableTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
2389
2394
|
self.device1.delete()
|
|
2390
2395
|
|
|
2391
2396
|
|
|
2397
|
+
class PowerFeedTestCase(ModelTestCases.BaseModelTestCase):
|
|
2398
|
+
model = PowerFeed
|
|
2399
|
+
|
|
2400
|
+
@classmethod
|
|
2401
|
+
def setUpTestData(cls):
|
|
2402
|
+
cls.location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
|
|
2403
|
+
cls.status = Status.objects.get_for_model(PowerFeed).first()
|
|
2404
|
+
cls.rack_status = Status.objects.get_for_model(Rack).first()
|
|
2405
|
+
|
|
2406
|
+
cls.source_panel = PowerPanel.objects.create(location=cls.location, name="Source Panel 1")
|
|
2407
|
+
cls.destination_panel = PowerPanel.objects.create(location=cls.location, name="Destination Panel 1")
|
|
2408
|
+
|
|
2409
|
+
# Create location in different hierarchy for rack validation test
|
|
2410
|
+
cls.other_location = Location.objects.create(
|
|
2411
|
+
name="Other Location",
|
|
2412
|
+
location_type=LocationType.objects.get(name="Campus"),
|
|
2413
|
+
status=Status.objects.get_for_model(Location).first(),
|
|
2414
|
+
)
|
|
2415
|
+
|
|
2416
|
+
cls.rack = Rack.objects.create(location=cls.location, name="Test Rack", status=cls.rack_status)
|
|
2417
|
+
cls.other_rack = Rack.objects.create(location=cls.other_location, name="Other Rack", status=cls.rack_status)
|
|
2418
|
+
|
|
2419
|
+
PowerFeed.objects.create(
|
|
2420
|
+
name="Test Power Feed 1",
|
|
2421
|
+
power_panel=cls.source_panel,
|
|
2422
|
+
rack=cls.rack,
|
|
2423
|
+
status=cls.status,
|
|
2424
|
+
)
|
|
2425
|
+
PowerFeed.objects.create(
|
|
2426
|
+
name="Test Power Feed 2",
|
|
2427
|
+
power_panel=cls.source_panel,
|
|
2428
|
+
status=cls.status,
|
|
2429
|
+
)
|
|
2430
|
+
PowerFeed.objects.create(
|
|
2431
|
+
name="Test Power Feed 3",
|
|
2432
|
+
power_panel=cls.destination_panel,
|
|
2433
|
+
status=cls.status,
|
|
2434
|
+
)
|
|
2435
|
+
|
|
2436
|
+
def test_destination_panel_self_reference_validation(self):
|
|
2437
|
+
"""Test that a power feed cannot reference itself."""
|
|
2438
|
+
feed = PowerFeed(
|
|
2439
|
+
name="Self Reference",
|
|
2440
|
+
power_panel=self.source_panel,
|
|
2441
|
+
destination_panel=self.source_panel,
|
|
2442
|
+
status=self.status,
|
|
2443
|
+
)
|
|
2444
|
+
|
|
2445
|
+
with self.assertRaises(ValidationError) as cm:
|
|
2446
|
+
feed.full_clean()
|
|
2447
|
+
self.assertIn("destination_panel", cm.exception.message_dict)
|
|
2448
|
+
|
|
2449
|
+
def test_circuit_breaker_position_conflict_validation(self):
|
|
2450
|
+
"""Test that overlapping circuit breaker positions raise validation errors."""
|
|
2451
|
+
# Create first feed at position 1 with 2 poles (occupies 1,3)
|
|
2452
|
+
PowerFeed.objects.create(
|
|
2453
|
+
name="Feed 1",
|
|
2454
|
+
power_panel=self.source_panel,
|
|
2455
|
+
status=self.status,
|
|
2456
|
+
breaker_position=1,
|
|
2457
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
|
|
2458
|
+
)
|
|
2459
|
+
|
|
2460
|
+
# Try to create conflicting feed at position 3
|
|
2461
|
+
conflicting_feed = PowerFeed(
|
|
2462
|
+
name="Feed 2",
|
|
2463
|
+
power_panel=self.source_panel,
|
|
2464
|
+
status=self.status,
|
|
2465
|
+
breaker_position=3,
|
|
2466
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
|
|
2467
|
+
)
|
|
2468
|
+
|
|
2469
|
+
with self.assertRaises(ValidationError) as cm:
|
|
2470
|
+
conflicting_feed.full_clean()
|
|
2471
|
+
self.assertIn("breaker_position", cm.exception.message_dict)
|
|
2472
|
+
|
|
2473
|
+
def test_rack_location_hierarchy_validation(self):
|
|
2474
|
+
"""Test that rack must belong to same location hierarchy as power panel."""
|
|
2475
|
+
feed = PowerFeed(
|
|
2476
|
+
name="Invalid Rack Location",
|
|
2477
|
+
power_panel=self.source_panel,
|
|
2478
|
+
rack=self.other_rack, # Different location hierarchy
|
|
2479
|
+
status=self.status,
|
|
2480
|
+
)
|
|
2481
|
+
|
|
2482
|
+
with self.assertRaises(ValidationError) as cm:
|
|
2483
|
+
feed.full_clean()
|
|
2484
|
+
self.assertIn("rack", cm.exception.message_dict)
|
|
2485
|
+
|
|
2486
|
+
def test_ac_voltage_negative_validation(self):
|
|
2487
|
+
"""Test that AC supply cannot have negative voltage."""
|
|
2488
|
+
feed = PowerFeed(
|
|
2489
|
+
name="Negative Voltage",
|
|
2490
|
+
power_panel=self.source_panel,
|
|
2491
|
+
status=self.status,
|
|
2492
|
+
voltage=-120,
|
|
2493
|
+
supply=PowerFeedSupplyChoices.SUPPLY_AC,
|
|
2494
|
+
)
|
|
2495
|
+
|
|
2496
|
+
with self.assertRaises(ValidationError) as cm:
|
|
2497
|
+
feed.full_clean()
|
|
2498
|
+
self.assertIn("voltage", cm.exception.message_dict)
|
|
2499
|
+
|
|
2500
|
+
def test_phase_designation_single_pole(self):
|
|
2501
|
+
"""Test phase designation calculation for single-pole breakers."""
|
|
2502
|
+
# Pattern: positions 1,2=A, 3,4=B, 5,6=C, 7,8=A, etc.
|
|
2503
|
+
test_cases = [
|
|
2504
|
+
(1, "A"),
|
|
2505
|
+
(2, "A"),
|
|
2506
|
+
(3, "B"),
|
|
2507
|
+
(4, "B"),
|
|
2508
|
+
(5, "C"),
|
|
2509
|
+
(6, "C"),
|
|
2510
|
+
(7, "A"),
|
|
2511
|
+
(8, "A"),
|
|
2512
|
+
(9, "B"),
|
|
2513
|
+
(10, "B"),
|
|
2514
|
+
]
|
|
2515
|
+
|
|
2516
|
+
for position, expected_phase in test_cases:
|
|
2517
|
+
with self.subTest(position=position):
|
|
2518
|
+
feed = PowerFeed.objects.create(
|
|
2519
|
+
name=f"1P Test {position}",
|
|
2520
|
+
power_panel=self.source_panel,
|
|
2521
|
+
status=self.status,
|
|
2522
|
+
breaker_position=position,
|
|
2523
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
|
|
2524
|
+
# phase defaults to PHASE_SINGLE which is correct for all single-pole feeds
|
|
2525
|
+
)
|
|
2526
|
+
self.assertEqual(feed.phase_designation, expected_phase)
|
|
2527
|
+
|
|
2528
|
+
def test_phase_designation_two_pole_single_phase(self):
|
|
2529
|
+
"""Test phase designation for 2-pole breakers delivering single-phase power."""
|
|
2530
|
+
# Common datacenter scenario: 2P breaker for 208V single-phase to rack PDU
|
|
2531
|
+
# Uses two phase conductors but delivers single-phase power
|
|
2532
|
+
test_cases = [
|
|
2533
|
+
(1, "A-B"), # occupies 1,3 → A,B → 208V single-phase
|
|
2534
|
+
(2, "A-B"), # occupies 2,4 → A,B → 208V single-phase
|
|
2535
|
+
(3, "B-C"), # occupies 3,5 → B,C → 208V single-phase
|
|
2536
|
+
(4, "B-C"), # occupies 4,6 → B,C → 208V single-phase
|
|
2537
|
+
(5, "A-C"), # occupies 5,7 → C,A → sorted to A-C → 208V single-phase
|
|
2538
|
+
(6, "A-C"), # occupies 6,8 → C,A → sorted to A-C → 208V single-phase
|
|
2539
|
+
(7, "A-B"), # occupies 7,9 → A,B → 208V single-phase (cycle continues)
|
|
2540
|
+
]
|
|
2541
|
+
|
|
2542
|
+
for position, expected_designation in test_cases:
|
|
2543
|
+
with self.subTest(position=position):
|
|
2544
|
+
feed = PowerFeed.objects.create(
|
|
2545
|
+
name=f"2P Single-Phase Test {position}",
|
|
2546
|
+
power_panel=self.source_panel,
|
|
2547
|
+
status=self.status,
|
|
2548
|
+
breaker_position=position,
|
|
2549
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
|
|
2550
|
+
# phase defaults to PHASE_SINGLE - correct for 208V single-phase feeds
|
|
2551
|
+
)
|
|
2552
|
+
self.assertEqual(feed.phase_designation, expected_designation)
|
|
2553
|
+
# Verify it's still marked as single-phase power
|
|
2554
|
+
self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_SINGLE)
|
|
2555
|
+
|
|
2556
|
+
def test_phase_designation_three_pole_three_phase(self):
|
|
2557
|
+
"""Test phase designation for 3-pole breakers delivering three-phase power."""
|
|
2558
|
+
# True three-phase power using all three phases
|
|
2559
|
+
test_cases = [1, 2, 3, 4, 5]
|
|
2560
|
+
|
|
2561
|
+
for position in test_cases:
|
|
2562
|
+
with self.subTest(position=position):
|
|
2563
|
+
feed = PowerFeed.objects.create(
|
|
2564
|
+
name=f"3P Three-Phase Test {position}",
|
|
2565
|
+
power_panel=self.source_panel,
|
|
2566
|
+
status=self.status,
|
|
2567
|
+
breaker_position=position,
|
|
2568
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_3,
|
|
2569
|
+
phase=PowerFeedPhaseChoices.PHASE_3PHASE, # Explicitly set to three-phase
|
|
2570
|
+
)
|
|
2571
|
+
self.assertEqual(feed.phase_designation, "A-B-C")
|
|
2572
|
+
# Verify it's marked as three-phase power
|
|
2573
|
+
self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_3PHASE)
|
|
2574
|
+
|
|
2575
|
+
def test_phase_designation_edge_cases(self):
|
|
2576
|
+
"""Test phase designation calculation for edge cases and None values."""
|
|
2577
|
+
# Test missing breaker_position
|
|
2578
|
+
feed = PowerFeed.objects.create(
|
|
2579
|
+
name="No Position",
|
|
2580
|
+
power_panel=self.source_panel,
|
|
2581
|
+
status=self.status,
|
|
2582
|
+
breaker_position=None,
|
|
2583
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_1,
|
|
2584
|
+
)
|
|
2585
|
+
self.assertIsNone(feed.phase_designation)
|
|
2586
|
+
|
|
2587
|
+
# Test missing breaker_pole_count (should default to single pole)
|
|
2588
|
+
feed = PowerFeed.objects.create(
|
|
2589
|
+
name="No Pole Count",
|
|
2590
|
+
power_panel=self.source_panel,
|
|
2591
|
+
status=self.status,
|
|
2592
|
+
breaker_position=1,
|
|
2593
|
+
breaker_pole_count=None,
|
|
2594
|
+
)
|
|
2595
|
+
# Should default to single pole and have phase designation
|
|
2596
|
+
self.assertEqual(feed.breaker_pole_count, PowerFeedBreakerPoleChoices.POLE_1)
|
|
2597
|
+
self.assertEqual(feed.phase_designation, "A")
|
|
2598
|
+
|
|
2599
|
+
# Test both missing
|
|
2600
|
+
feed = PowerFeed.objects.create(
|
|
2601
|
+
name="No Position or Pole Count",
|
|
2602
|
+
power_panel=self.source_panel,
|
|
2603
|
+
status=self.status,
|
|
2604
|
+
breaker_position=None,
|
|
2605
|
+
breaker_pole_count=None,
|
|
2606
|
+
)
|
|
2607
|
+
self.assertIsNone(feed.phase_designation)
|
|
2608
|
+
|
|
2609
|
+
def test_phase_field_defaults(self):
|
|
2610
|
+
"""Test that phase field defaults correctly and type field defaults to primary."""
|
|
2611
|
+
feed = PowerFeed.objects.create(
|
|
2612
|
+
name="Default Fields Test",
|
|
2613
|
+
power_panel=self.source_panel,
|
|
2614
|
+
status=self.status,
|
|
2615
|
+
)
|
|
2616
|
+
|
|
2617
|
+
# Verify defaults
|
|
2618
|
+
self.assertEqual(feed.phase, PowerFeedPhaseChoices.PHASE_SINGLE)
|
|
2619
|
+
self.assertEqual(feed.type, PowerFeedTypeChoices.TYPE_PRIMARY)
|
|
2620
|
+
|
|
2621
|
+
def test_occupied_positions(self):
|
|
2622
|
+
"""Test occupied positions calculation."""
|
|
2623
|
+
feed = PowerFeed.objects.create(
|
|
2624
|
+
name="Test Feed",
|
|
2625
|
+
power_panel=self.source_panel,
|
|
2626
|
+
status=self.status,
|
|
2627
|
+
breaker_position=5,
|
|
2628
|
+
breaker_pole_count=PowerFeedBreakerPoleChoices.POLE_2,
|
|
2629
|
+
)
|
|
2630
|
+
|
|
2631
|
+
self.assertEqual(feed.get_occupied_positions(), {5, 7})
|
|
2632
|
+
self.assertEqual(feed.occupied_positions, "5, 7")
|
|
2633
|
+
|
|
2634
|
+
def test_breaker_pole_count_enforcement_in_save(self):
|
|
2635
|
+
"""Test that breaker_pole_count defaults to POLE_1 when breaker_position is set during save."""
|
|
2636
|
+
# Create feed with breaker_position but no breaker_pole_count
|
|
2637
|
+
feed = PowerFeed(
|
|
2638
|
+
name="Test Save Enforcement",
|
|
2639
|
+
power_panel=self.source_panel,
|
|
2640
|
+
status=self.status,
|
|
2641
|
+
breaker_position=10,
|
|
2642
|
+
)
|
|
2643
|
+
|
|
2644
|
+
# Verify breaker_pole_count is None before save
|
|
2645
|
+
self.assertIsNone(feed.breaker_pole_count)
|
|
2646
|
+
|
|
2647
|
+
# Save without calling clean() to bypass form validation
|
|
2648
|
+
feed.save()
|
|
2649
|
+
|
|
2650
|
+
# Verify breaker_pole_count was set to POLE_1 during save
|
|
2651
|
+
self.assertEqual(feed.breaker_pole_count, PowerFeedBreakerPoleChoices.POLE_1)
|
|
2652
|
+
|
|
2653
|
+
|
|
2392
2654
|
class PowerPanelTestCase(TestCase): # TODO: change to BaseModelTestCase once we have a PowerPanelFactory
|
|
2393
2655
|
def test_power_panel_validation(self):
|
|
2394
2656
|
status = Status.objects.get_for_model(Location).first()
|