nautobot 2.2.7__py3-none-any.whl → 2.2.8__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/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +7 -4
- nautobot/core/tests/integration/test_general_functionality.py +36 -0
- nautobot/core/tests/test_graphql.py +51 -5
- nautobot/dcim/models/devices.py +10 -3
- nautobot/dcim/tests/test_models.py +75 -0
- nautobot/extras/models/relationships.py +12 -0
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_relationships.py +221 -1
- nautobot/project-static/docs/404.html +2 -2
- nautobot/project-static/docs/apps/index.html +2 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css → main.76a95c52.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css.map → main.76a95c52.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
- nautobot/project-static/docs/development/apps/api/setup.html +2 -2
- nautobot/project-static/docs/development/apps/api/testing.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
- nautobot/project-static/docs/development/apps/index.html +2 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
- nautobot/project-static/docs/development/core/application-registry.html +2 -2
- nautobot/project-static/docs/development/core/best-practices.html +2 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
- nautobot/project-static/docs/development/core/caching.html +2 -2
- nautobot/project-static/docs/development/core/controllers.html +2 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
- nautobot/project-static/docs/development/core/generic-views.html +2 -2
- nautobot/project-static/docs/development/core/getting-started.html +2 -2
- nautobot/project-static/docs/development/core/homepage.html +2 -2
- nautobot/project-static/docs/development/core/index.html +2 -2
- nautobot/project-static/docs/development/core/model-checklist.html +2 -2
- nautobot/project-static/docs/development/core/model-features.html +2 -2
- nautobot/project-static/docs/development/core/natural-keys.html +2 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
- nautobot/project-static/docs/development/core/release-checklist.html +2 -2
- nautobot/project-static/docs/development/core/role-internals.html +2 -2
- nautobot/project-static/docs/development/core/settings.html +2 -2
- nautobot/project-static/docs/development/core/style-guide.html +2 -2
- nautobot/project-static/docs/development/core/templates.html +2 -2
- nautobot/project-static/docs/development/core/testing.html +2 -2
- nautobot/project-static/docs/development/core/user-preferences.html +2 -2
- nautobot/project-static/docs/development/index.html +2 -2
- nautobot/project-static/docs/development/jobs/index.html +2 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
- nautobot/project-static/docs/overview/application_stack.html +2 -2
- nautobot/project-static/docs/overview/design_philosophy.html +2 -2
- nautobot/project-static/docs/overview/index.html +2 -2
- nautobot/project-static/docs/release-notes/index.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +254 -108
- nautobot/project-static/docs/requirements.txt +2 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +256 -256
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +6 -6
- nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +7 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
- nautobot/project-static/docs/user-guide/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/METADATA +3 -3
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/RECORD +278 -277
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/NOTICE +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/WHEEL +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.8.dist-info}/entry_points.txt +0 -0
|
@@ -55,12 +55,12 @@ def generate_null_choices_resolver(name, resolver_name):
|
|
|
55
55
|
|
|
56
56
|
def generate_filter_resolver(schema_type, resolver_name, field_name):
|
|
57
57
|
"""
|
|
58
|
-
Generate function to resolve
|
|
58
|
+
Generate function to resolve filtering of ManyToOne and ManyToMany related objects.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
61
|
schema_type (DjangoObjectType): DjangoObjectType for a given model
|
|
62
62
|
resolver_name (str): name of the resolver
|
|
63
|
-
field_name (str): name of
|
|
63
|
+
field_name (str): name of ManyToOneField, ManyToManyRel, or ManyToOneRel field to filter
|
|
64
64
|
"""
|
|
65
65
|
filterset_class = schema_type._meta.filterset_class
|
|
66
66
|
|
nautobot/core/graphql/schema.py
CHANGED
|
@@ -6,7 +6,8 @@ import logging
|
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from django.contrib.contenttypes.models import ContentType
|
|
8
8
|
from django.core.validators import ValidationError
|
|
9
|
-
from django.db.models
|
|
9
|
+
from django.db.models import ManyToManyField
|
|
10
|
+
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel
|
|
10
11
|
import graphene
|
|
11
12
|
from graphene.types import generic
|
|
12
13
|
|
|
@@ -198,9 +199,11 @@ def extend_schema_type_filter(schema_type, model):
|
|
|
198
199
|
(DjangoObjectType): The extended schema_type object
|
|
199
200
|
"""
|
|
200
201
|
for field in model._meta.get_fields():
|
|
201
|
-
# Check attribute is a ManyToOne field
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
# Check whether attribute is a ManyToOne or ManyToMany field
|
|
203
|
+
if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel)):
|
|
204
|
+
continue
|
|
205
|
+
# OneToOneRel is a subclass of ManyToOneRel, but we don't want to treat it as a list
|
|
206
|
+
if isinstance(field, OneToOneRel):
|
|
204
207
|
continue
|
|
205
208
|
child_schema_type = registry["graphql_types"].get(field.related_model._meta.label_lower)
|
|
206
209
|
if child_schema_type:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from django.urls import reverse
|
|
2
|
+
|
|
3
|
+
from nautobot.core.testing.integration import SeleniumTestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StaticMediaFailureTestCase(SeleniumTestCase):
|
|
7
|
+
"""Integration test to make sure no static media failures are encountered."""
|
|
8
|
+
|
|
9
|
+
def setUp(self):
|
|
10
|
+
super().setUp()
|
|
11
|
+
self.user.is_superuser = True
|
|
12
|
+
self.user.is_staff = True
|
|
13
|
+
self.user.save()
|
|
14
|
+
self.login(self.user.username, self.password)
|
|
15
|
+
|
|
16
|
+
def tearDown(self):
|
|
17
|
+
self.logout()
|
|
18
|
+
super().tearDown()
|
|
19
|
+
|
|
20
|
+
def test_for_static_media_failure(self):
|
|
21
|
+
test_urls = [
|
|
22
|
+
reverse("home"),
|
|
23
|
+
reverse("api-root"),
|
|
24
|
+
reverse("graphql"),
|
|
25
|
+
reverse("api_docs"),
|
|
26
|
+
"/admin/",
|
|
27
|
+
"/static/docs/overview/index.html",
|
|
28
|
+
]
|
|
29
|
+
for url in test_urls:
|
|
30
|
+
with self.subTest(test_url=url):
|
|
31
|
+
self.browser.visit(self.live_server_url + url)
|
|
32
|
+
# Wait for body element to appear
|
|
33
|
+
self.assertTrue(self.browser.is_element_present_by_tag("body", wait_time=10), "Page failed to load")
|
|
34
|
+
# Ensure we weren't redirected to another page
|
|
35
|
+
self.assertEqual(self.browser.url, self.live_server_url + url)
|
|
36
|
+
self.assertTrue(self.browser.is_text_not_present("Static Media Failure"))
|
|
@@ -729,8 +729,8 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
729
729
|
cls.location_type = LocationType.objects.get(name="Campus")
|
|
730
730
|
cls.location1 = Location.objects.filter(location_type=cls.location_type).first()
|
|
731
731
|
cls.location2 = Location.objects.filter(location_type=cls.location_type).last()
|
|
732
|
-
cls.location1.name = "Location-1"
|
|
733
|
-
cls.location2.name = "Location-2"
|
|
732
|
+
cls.location1.name = "Campus Location-1"
|
|
733
|
+
cls.location2.name = "Campus Location-2"
|
|
734
734
|
cls.location1.status = cls.location_statuses[0]
|
|
735
735
|
cls.location2.status = cls.location_statuses[1]
|
|
736
736
|
cls.location1.validated_save()
|
|
@@ -930,6 +930,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
930
930
|
cls.prefix1 = Prefix.objects.create(
|
|
931
931
|
prefix="10.0.1.0/24", namespace=cls.namespace, status=cls.prefix_statuses[0]
|
|
932
932
|
)
|
|
933
|
+
cls.prefix1.locations.add(cls.location1, cls.location2)
|
|
933
934
|
cls.ipaddr1 = IPAddress.objects.create(
|
|
934
935
|
address="10.0.1.1/24", namespace=cls.namespace, status=cls.ip_statuses[0]
|
|
935
936
|
)
|
|
@@ -968,6 +969,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
968
969
|
cls.prefix2 = Prefix.objects.create(
|
|
969
970
|
prefix="10.0.2.0/24", namespace=cls.namespace, status=cls.prefix_statuses[1]
|
|
970
971
|
)
|
|
972
|
+
cls.prefix2.locations.add(cls.location1, cls.location2)
|
|
971
973
|
cls.ipaddr2 = IPAddress.objects.create(
|
|
972
974
|
address="10.0.2.1/30", namespace=cls.namespace, status=cls.ip_statuses[1]
|
|
973
975
|
)
|
|
@@ -1507,9 +1509,9 @@ query {
|
|
|
1507
1509
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1508
1510
|
def test_query_locations_filter(self):
|
|
1509
1511
|
filters = (
|
|
1510
|
-
('name: "Location-1"', 1),
|
|
1511
|
-
('name: ["Location-1"]', 1),
|
|
1512
|
-
('name: ["Location-1", "Location-2"]', 2),
|
|
1512
|
+
('name: "Campus Location-1"', 1),
|
|
1513
|
+
('name: ["Campus Location-1"]', 1),
|
|
1514
|
+
('name: ["Campus Location-1", "Campus Location-2"]', 2),
|
|
1513
1515
|
('name__ic: "Location"', Location.objects.filter(name__icontains="Location").count()),
|
|
1514
1516
|
('name__ic: ["Location"]', Location.objects.filter(name__icontains="Location").count()),
|
|
1515
1517
|
('name__nic: "Location"', Location.objects.exclude(name__icontains="Location").count()),
|
|
@@ -1541,6 +1543,50 @@ query {
|
|
|
1541
1543
|
self.assertIsNone(result.errors)
|
|
1542
1544
|
self.assertEqual(len(result.data["locations"]), nbr_expected_results)
|
|
1543
1545
|
|
|
1546
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1547
|
+
def test_query_prefixes_nested_m2m_filter(self):
|
|
1548
|
+
"""
|
|
1549
|
+
Test functionality added to address https://github.com/nautobot/nautobot/issues/5906.
|
|
1550
|
+
|
|
1551
|
+
Prefix.locations is a ManyToManyField, which was not filterable in our GraphQL schema before this fix.
|
|
1552
|
+
"""
|
|
1553
|
+
query = 'query { prefixes (prefix_length__gte:16) { prefix locations (location_type:["Campus"]) { name } } }'
|
|
1554
|
+
result = self.execute_query(query)
|
|
1555
|
+
self.assertIsNone(result.errors)
|
|
1556
|
+
found_valid_location = False
|
|
1557
|
+
found_invalid_location = False
|
|
1558
|
+
for prefix_data in result.data["prefixes"]:
|
|
1559
|
+
for location_data in prefix_data["locations"]:
|
|
1560
|
+
if location_data["name"].startswith("Campus"):
|
|
1561
|
+
found_valid_location = True
|
|
1562
|
+
else:
|
|
1563
|
+
print(f"Found unexpected unfiltered location {location_data['name']} under {prefix_data['prefix']}")
|
|
1564
|
+
found_invalid_location = True
|
|
1565
|
+
self.assertTrue(found_valid_location)
|
|
1566
|
+
self.assertFalse(found_invalid_location)
|
|
1567
|
+
|
|
1568
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1569
|
+
def test_query_locations_nested_reverse_m2m_filter(self):
|
|
1570
|
+
"""
|
|
1571
|
+
Test functionality added to address https://github.com/nautobot/nautobot/issues/5906.
|
|
1572
|
+
|
|
1573
|
+
Location.prefixes is a (reverse) ManyToManyRel, which was not filterable in our GraphQL schema before this fix.
|
|
1574
|
+
"""
|
|
1575
|
+
query = "query { locations { name prefixes (prefix_length:24) { prefix } } }"
|
|
1576
|
+
result = self.execute_query(query)
|
|
1577
|
+
self.assertIsNone(result.errors)
|
|
1578
|
+
found_valid_prefix = False
|
|
1579
|
+
found_invalid_prefix = False
|
|
1580
|
+
for location_data in result.data["locations"]:
|
|
1581
|
+
for prefix_data in location_data["prefixes"]:
|
|
1582
|
+
if prefix_data["prefix"].endswith("/24"):
|
|
1583
|
+
found_valid_prefix = True
|
|
1584
|
+
else:
|
|
1585
|
+
print(f"Found unexpected unfiltered prefix {prefix_data['prefix']} under {location_data['name']}")
|
|
1586
|
+
found_invalid_prefix = True
|
|
1587
|
+
self.assertTrue(found_valid_prefix)
|
|
1588
|
+
self.assertFalse(found_invalid_prefix)
|
|
1589
|
+
|
|
1544
1590
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1545
1591
|
def test_query_devices_filter(self):
|
|
1546
1592
|
filterset_class = DeviceFilterSet
|
nautobot/dcim/models/devices.py
CHANGED
|
@@ -832,9 +832,16 @@ class Device(PrimaryModel, ConfigContextModel):
|
|
|
832
832
|
# Update Location and Rack assignment for any child Devices
|
|
833
833
|
devices = Device.objects.filter(parent_bay__device=self)
|
|
834
834
|
for device in devices:
|
|
835
|
-
|
|
836
|
-
device.
|
|
837
|
-
|
|
835
|
+
save_child_device = False
|
|
836
|
+
if device.location != self.location:
|
|
837
|
+
device.location = self.location
|
|
838
|
+
save_child_device = True
|
|
839
|
+
if device.rack != self.rack:
|
|
840
|
+
device.rack = self.rack
|
|
841
|
+
save_child_device = True
|
|
842
|
+
|
|
843
|
+
if save_child_device:
|
|
844
|
+
device.save()
|
|
838
845
|
|
|
839
846
|
def create_components(self):
|
|
840
847
|
"""Create device components from the device type definition."""
|
|
@@ -16,6 +16,7 @@ from nautobot.dcim.choices import (
|
|
|
16
16
|
InterfaceTypeChoices,
|
|
17
17
|
PortTypeChoices,
|
|
18
18
|
PowerOutletFeedLegChoices,
|
|
19
|
+
SubdeviceRoleChoices,
|
|
19
20
|
)
|
|
20
21
|
from nautobot.dcim.models import (
|
|
21
22
|
Cable,
|
|
@@ -886,6 +887,13 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
886
887
|
self.device_type = DeviceType.objects.create(
|
|
887
888
|
manufacturer=manufacturer,
|
|
888
889
|
model="Test Device Type 1",
|
|
890
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_PARENT,
|
|
891
|
+
)
|
|
892
|
+
self.child_devicetype = DeviceType.objects.create(
|
|
893
|
+
model="Child Device Type 1",
|
|
894
|
+
manufacturer=manufacturer,
|
|
895
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
|
|
896
|
+
u_height=0,
|
|
889
897
|
)
|
|
890
898
|
self.device_role = Role.objects.get_for_model(Device).first()
|
|
891
899
|
self.device_status = Status.objects.get_for_model(Device).first()
|
|
@@ -1188,6 +1196,73 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1188
1196
|
self.device_type.software_image_files.add(software_image_file)
|
|
1189
1197
|
self.device.validated_save()
|
|
1190
1198
|
|
|
1199
|
+
def test_child_devices_are_not_saved_when_unnecessary(self):
|
|
1200
|
+
parent_device = Device.objects.create(
|
|
1201
|
+
name="Parent Device 1",
|
|
1202
|
+
location=self.location_3,
|
|
1203
|
+
device_type=self.device_type,
|
|
1204
|
+
role=self.device_role,
|
|
1205
|
+
status=self.device_status,
|
|
1206
|
+
)
|
|
1207
|
+
parent_device.validated_save()
|
|
1208
|
+
|
|
1209
|
+
child_device = Device.objects.create(
|
|
1210
|
+
name="Child Device 1",
|
|
1211
|
+
location=parent_device.location,
|
|
1212
|
+
device_type=self.child_devicetype,
|
|
1213
|
+
role=parent_device.role,
|
|
1214
|
+
status=self.device_status,
|
|
1215
|
+
)
|
|
1216
|
+
child_device.validated_save()
|
|
1217
|
+
child_mtime_before_parent_saved = str(child_device.last_updated)
|
|
1218
|
+
|
|
1219
|
+
devicebay = DeviceBay.objects.get(device=parent_device, name="Device Bay 1")
|
|
1220
|
+
devicebay.installed_device = child_device
|
|
1221
|
+
devicebay.validated_save()
|
|
1222
|
+
|
|
1223
|
+
#
|
|
1224
|
+
# Tests
|
|
1225
|
+
#
|
|
1226
|
+
|
|
1227
|
+
#
|
|
1228
|
+
# On a NOOP save, the child device shouldn't be updated
|
|
1229
|
+
parent_device.save()
|
|
1230
|
+
|
|
1231
|
+
child_mtime_after_parent_noop_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1232
|
+
|
|
1233
|
+
self.assertEqual(child_mtime_before_parent_saved, child_mtime_after_parent_noop_save)
|
|
1234
|
+
|
|
1235
|
+
#
|
|
1236
|
+
# On a serial number update, the child device shouldn't be updated
|
|
1237
|
+
parent_device.serial = "12345"
|
|
1238
|
+
parent_device.save()
|
|
1239
|
+
|
|
1240
|
+
child_mtime_after_parent_serial_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1241
|
+
|
|
1242
|
+
self.assertEqual(child_mtime_before_parent_saved, child_mtime_after_parent_serial_update_save)
|
|
1243
|
+
|
|
1244
|
+
#
|
|
1245
|
+
# If the parent rack updates, the child mtime should update.
|
|
1246
|
+
rack = Rack.objects.create(name="Rack 1", location=parent_device.location, status=self.device_status)
|
|
1247
|
+
parent_device.rack = rack
|
|
1248
|
+
parent_device.save()
|
|
1249
|
+
|
|
1250
|
+
child_mtime_after_parent_rack_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1251
|
+
|
|
1252
|
+
self.assertNotEqual(child_mtime_after_parent_noop_save, child_mtime_after_parent_rack_update_save)
|
|
1253
|
+
|
|
1254
|
+
#
|
|
1255
|
+
# If the parent site updates, the child mtime should update
|
|
1256
|
+
location = Location.objects.create(
|
|
1257
|
+
name="New Site 1", status=self.device_status, location_type=self.location_type_3
|
|
1258
|
+
)
|
|
1259
|
+
parent_device.location = location
|
|
1260
|
+
parent_device.save()
|
|
1261
|
+
|
|
1262
|
+
child_mtime_after_parent_site_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1263
|
+
|
|
1264
|
+
self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
|
|
1265
|
+
|
|
1191
1266
|
|
|
1192
1267
|
class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
|
|
1193
1268
|
model = DeviceTypeToSoftwareImageFile
|
|
@@ -244,6 +244,18 @@ class RelationshipModel(models.Model):
|
|
|
244
244
|
if relation.skip_required(cls, opposite_side):
|
|
245
245
|
continue
|
|
246
246
|
|
|
247
|
+
if getattr(relation, f"{relation.required_on}_filter") and instance:
|
|
248
|
+
filterset = get_filterset_for_model(cls)
|
|
249
|
+
if filterset:
|
|
250
|
+
filter_params = getattr(relation, f"{relation.required_on}_filter")
|
|
251
|
+
# If the relationship is required on the model, but the object is not in the filter,
|
|
252
|
+
# we should allow the object to be saved, as the object is not part of the relationship.
|
|
253
|
+
# Example: We want a Device with a Role of Switch to be required to have a relationship
|
|
254
|
+
# with a Device that has a Role of Router. A Device with a Role of Printer should
|
|
255
|
+
# be exempt from the requirement.
|
|
256
|
+
if not filterset(filter_params, cls.objects.filter(id=instance.id)).qs.exists():
|
|
257
|
+
continue
|
|
258
|
+
|
|
247
259
|
if relation.has_many(opposite_side):
|
|
248
260
|
num_required_verbose = "at least one"
|
|
249
261
|
else:
|
|
@@ -74,7 +74,7 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
74
74
|
|
|
75
75
|
# And just a cursory check to make sure that the filter worked.
|
|
76
76
|
group = DynamicGroup.objects.get(name=name)
|
|
77
|
-
self.assertEqual(group.count,
|
|
77
|
+
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
78
78
|
self.assertEqual(group.filter, {"status": ["Active"]})
|
|
79
79
|
|
|
80
80
|
# Verify dynamic group shows up on device detail tab
|
|
@@ -15,9 +15,11 @@ from nautobot.core.tables import RelationshipColumn
|
|
|
15
15
|
from nautobot.core.testing import TestCase
|
|
16
16
|
from nautobot.core.testing.models import ModelTestCases
|
|
17
17
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
18
|
+
from nautobot.dcim.forms import DeviceForm
|
|
18
19
|
from nautobot.dcim.models import (
|
|
19
20
|
Controller,
|
|
20
21
|
Device,
|
|
22
|
+
DeviceType,
|
|
21
23
|
DeviceTypeToSoftwareImageFile,
|
|
22
24
|
Location,
|
|
23
25
|
LocationType,
|
|
@@ -27,7 +29,7 @@ from nautobot.dcim.models import (
|
|
|
27
29
|
from nautobot.dcim.tables import LocationTable
|
|
28
30
|
from nautobot.dcim.tests.test_views import create_test_device
|
|
29
31
|
from nautobot.extras.choices import RelationshipRequiredSideChoices, RelationshipSideChoices, RelationshipTypeChoices
|
|
30
|
-
from nautobot.extras.models import Relationship, RelationshipAssociation, Status
|
|
32
|
+
from nautobot.extras.models import Relationship, RelationshipAssociation, Role, Status
|
|
31
33
|
from nautobot.ipam.models import VLAN, VLANGroup
|
|
32
34
|
|
|
33
35
|
|
|
@@ -486,6 +488,224 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
486
488
|
with self.assertNumQueries(0):
|
|
487
489
|
manager_method(Location)
|
|
488
490
|
|
|
491
|
+
def test_required_related_object_errors(self):
|
|
492
|
+
"""
|
|
493
|
+
Confirm that the fix in https://github.com/nautobot/nautobot/pull/5570 is working as expected
|
|
494
|
+
"""
|
|
495
|
+
device_ct = ContentType.objects.get_for_model(Device)
|
|
496
|
+
status = Status.objects.get_for_model(Device).first()
|
|
497
|
+
device_type = DeviceType.objects.exclude(manufacturer__isnull=True).first()
|
|
498
|
+
# Create a Device with role Role 1
|
|
499
|
+
role_1 = Role.objects.create(name="Role 1")
|
|
500
|
+
role_1.content_types.add(ContentType.objects.get_for_model(Device))
|
|
501
|
+
device_1 = Device.objects.create(
|
|
502
|
+
device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
|
|
503
|
+
)
|
|
504
|
+
# Create a Device with role Role 2
|
|
505
|
+
role_2 = Role.objects.create(name="Role 2")
|
|
506
|
+
role_2.content_types.add(ContentType.objects.get_for_model(Device))
|
|
507
|
+
device_2 = Device.objects.create(
|
|
508
|
+
device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
|
|
509
|
+
)
|
|
510
|
+
# Create a Device with role Role 3
|
|
511
|
+
role_3 = Role.objects.create(name="Role 3")
|
|
512
|
+
role_3.content_types.add(ContentType.objects.get_for_model(Device))
|
|
513
|
+
device_3 = Device.objects.create(
|
|
514
|
+
device_type=device_type, role=role_3, name="Device 3", location=self.locations[0], status=status
|
|
515
|
+
)
|
|
516
|
+
# Create a one-to-many relationship with destination required, source filter: {"role": ["Role 1"]}
|
|
517
|
+
# and destination filter {"role": ["Role 2"]}
|
|
518
|
+
relationship = Relationship.objects.create(
|
|
519
|
+
label="Device to Devices",
|
|
520
|
+
key="device_to_devices",
|
|
521
|
+
source_type=device_ct,
|
|
522
|
+
source_filter={"role": ["Role 1"]},
|
|
523
|
+
destination_type=device_ct,
|
|
524
|
+
destination_filter={"role": ["Role 2"]},
|
|
525
|
+
type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
|
|
526
|
+
required_on="destination",
|
|
527
|
+
)
|
|
528
|
+
# Attempt to update device_3 which will not be in the queryset filtered by the destination filter
|
|
529
|
+
# Assert that the form is valid and no ValueError is raised.
|
|
530
|
+
update_status = Status.objects.get_for_model(Device).last()
|
|
531
|
+
update_data_for_device_3 = {
|
|
532
|
+
"location": device_3.location.pk,
|
|
533
|
+
"device_type": device_3.device_type.pk,
|
|
534
|
+
"role": device_3.role.pk,
|
|
535
|
+
"name": device_3.name,
|
|
536
|
+
"status": update_status.pk,
|
|
537
|
+
}
|
|
538
|
+
form = DeviceForm(instance=device_3, data=update_data_for_device_3)
|
|
539
|
+
self.assertTrue(form.is_valid())
|
|
540
|
+
# Attempt to update device_1 which will not be in the destination filter,
|
|
541
|
+
# but is in the source filter.
|
|
542
|
+
update_data_for_device_1 = {
|
|
543
|
+
"location": device_1.location.pk,
|
|
544
|
+
"device_type": device_1.device_type.pk,
|
|
545
|
+
"role": device_1.role.pk,
|
|
546
|
+
"name": device_1.name,
|
|
547
|
+
"status": update_status.pk,
|
|
548
|
+
}
|
|
549
|
+
form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
550
|
+
self.assertTrue(form2.is_valid())
|
|
551
|
+
# Attempt to update device_2 which will be in the destination filter, so it should
|
|
552
|
+
# require the relationship.
|
|
553
|
+
update_data_for_device_2 = {
|
|
554
|
+
"location": device_2.location.pk,
|
|
555
|
+
"device_type": device_2.device_type.pk,
|
|
556
|
+
"role": device_2.role.pk,
|
|
557
|
+
"name": "Device 2",
|
|
558
|
+
"status": update_status.pk,
|
|
559
|
+
}
|
|
560
|
+
form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
561
|
+
self.assertFalse(form3.is_valid())
|
|
562
|
+
# Device 1 has a relationship to Device 2
|
|
563
|
+
update_data_for_device_1 = {
|
|
564
|
+
"location": device_1.location.pk,
|
|
565
|
+
"device_type": device_1.device_type.pk,
|
|
566
|
+
"role": device_1.role.pk,
|
|
567
|
+
"name": device_1.name,
|
|
568
|
+
"status": update_status.pk,
|
|
569
|
+
"cr_device_to_devices__destination": [device_2.pk],
|
|
570
|
+
}
|
|
571
|
+
form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
572
|
+
self.assertTrue(form4.is_valid())
|
|
573
|
+
form4.save()
|
|
574
|
+
# Device 2 has a relationship to Device 1, form should validate and save.
|
|
575
|
+
update_data_for_device_2 = {
|
|
576
|
+
"location": device_2.location.pk,
|
|
577
|
+
"device_type": device_2.device_type.pk,
|
|
578
|
+
"role": device_2.role.pk,
|
|
579
|
+
"name": "Device 2",
|
|
580
|
+
"status": update_status.pk,
|
|
581
|
+
"cr_device_to_devices__source": device_1.pk,
|
|
582
|
+
}
|
|
583
|
+
form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
584
|
+
self.assertTrue(form5.is_valid())
|
|
585
|
+
form5.save()
|
|
586
|
+
# Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
587
|
+
update_data_for_device_2 = {
|
|
588
|
+
"location": device_2.location.pk,
|
|
589
|
+
"device_type": device_2.device_type.pk,
|
|
590
|
+
"role": device_2.role.pk,
|
|
591
|
+
"name": "Device 2",
|
|
592
|
+
"status": update_status.pk,
|
|
593
|
+
"cr_device_to_devices__source": device_3.pk,
|
|
594
|
+
}
|
|
595
|
+
form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
596
|
+
with self.assertRaises(ValidationError):
|
|
597
|
+
form6.save()
|
|
598
|
+
# Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
599
|
+
update_data_for_device_1 = {
|
|
600
|
+
"location": device_1.location.pk,
|
|
601
|
+
"device_type": device_1.device_type.pk,
|
|
602
|
+
"role": device_1.role.pk,
|
|
603
|
+
"name": "Device 1",
|
|
604
|
+
"status": update_status.pk,
|
|
605
|
+
"cr_device_to_devices__destination": [
|
|
606
|
+
device_3.pk,
|
|
607
|
+
],
|
|
608
|
+
}
|
|
609
|
+
form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
610
|
+
with self.assertRaises(ValidationError):
|
|
611
|
+
form6.save()
|
|
612
|
+
|
|
613
|
+
relationship.required_on = "source"
|
|
614
|
+
relationship.save()
|
|
615
|
+
# Attempt to update device_3 which will not be in the queryset filtered by the destination filter
|
|
616
|
+
# Assert that the form is valid and no ValueError is raised. This ensures that an object that
|
|
617
|
+
# does not take part in any relationships can still be updated, addressing issue #5569.
|
|
618
|
+
update_status = Status.objects.get_for_model(Device).last()
|
|
619
|
+
update_data_for_device_3 = {
|
|
620
|
+
"location": device_3.location.pk,
|
|
621
|
+
"device_type": device_3.device_type.pk,
|
|
622
|
+
"role": device_3.role.pk,
|
|
623
|
+
"name": device_3.name,
|
|
624
|
+
"status": update_status.pk,
|
|
625
|
+
}
|
|
626
|
+
form = DeviceForm(instance=device_3, data=update_data_for_device_3)
|
|
627
|
+
self.assertTrue(form.is_valid())
|
|
628
|
+
# Attempt to update device_1 which will not be in the destination filter,
|
|
629
|
+
# but is in the source filter. Should fail as device_to_devices is required.
|
|
630
|
+
device_1.delete()
|
|
631
|
+
device_1 = Device.objects.create(
|
|
632
|
+
device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
|
|
633
|
+
)
|
|
634
|
+
update_data_for_device_1 = {
|
|
635
|
+
"location": device_1.location.pk,
|
|
636
|
+
"device_type": device_1.device_type.pk,
|
|
637
|
+
"role": device_1.role.pk,
|
|
638
|
+
"name": device_1.name,
|
|
639
|
+
"status": update_status.pk,
|
|
640
|
+
}
|
|
641
|
+
form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
642
|
+
self.assertFalse(form2.is_valid())
|
|
643
|
+
# Attempt to update device_2 which will be in the destination filter, which should not require the
|
|
644
|
+
# relationship anymore.
|
|
645
|
+
device_2.delete()
|
|
646
|
+
device_2 = Device.objects.create(
|
|
647
|
+
device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
|
|
648
|
+
)
|
|
649
|
+
update_data_for_device_2 = {
|
|
650
|
+
"location": device_2.location.pk,
|
|
651
|
+
"device_type": device_2.device_type.pk,
|
|
652
|
+
"role": device_2.role.pk,
|
|
653
|
+
"name": "Device 2",
|
|
654
|
+
"status": update_status.pk,
|
|
655
|
+
}
|
|
656
|
+
form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
657
|
+
self.assertTrue(form3.is_valid())
|
|
658
|
+
# Device 1 has a relationship to Device 2
|
|
659
|
+
update_data_for_device_1 = {
|
|
660
|
+
"location": device_1.location.pk,
|
|
661
|
+
"device_type": device_1.device_type.pk,
|
|
662
|
+
"role": device_1.role.pk,
|
|
663
|
+
"name": device_1.name,
|
|
664
|
+
"status": update_status.pk,
|
|
665
|
+
"cr_device_to_devices__destination": [device_2.pk],
|
|
666
|
+
}
|
|
667
|
+
form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
668
|
+
self.assertTrue(form4.is_valid())
|
|
669
|
+
form4.save()
|
|
670
|
+
# Device 2 has a relationship to Device 1, form should validate and save.
|
|
671
|
+
update_data_for_device_2 = {
|
|
672
|
+
"location": device_2.location.pk,
|
|
673
|
+
"device_type": device_2.device_type.pk,
|
|
674
|
+
"role": device_2.role.pk,
|
|
675
|
+
"name": "Device 2",
|
|
676
|
+
"status": update_status.pk,
|
|
677
|
+
"cr_device_to_devices__source": device_1.pk,
|
|
678
|
+
}
|
|
679
|
+
form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
680
|
+
self.assertTrue(form5.is_valid())
|
|
681
|
+
form5.save()
|
|
682
|
+
# Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
683
|
+
update_data_for_device_2 = {
|
|
684
|
+
"location": device_2.location.pk,
|
|
685
|
+
"device_type": device_2.device_type.pk,
|
|
686
|
+
"role": device_2.role.pk,
|
|
687
|
+
"name": "Device 2",
|
|
688
|
+
"status": update_status.pk,
|
|
689
|
+
"cr_device_to_devices__source": device_3.pk,
|
|
690
|
+
}
|
|
691
|
+
form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
692
|
+
with self.assertRaises(ValidationError):
|
|
693
|
+
form6.save()
|
|
694
|
+
# Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
695
|
+
update_data_for_device_1 = {
|
|
696
|
+
"location": device_1.location.pk,
|
|
697
|
+
"device_type": device_1.device_type.pk,
|
|
698
|
+
"role": device_1.role.pk,
|
|
699
|
+
"name": "Device 1",
|
|
700
|
+
"status": update_status.pk,
|
|
701
|
+
"cr_device_to_devices__destination": [
|
|
702
|
+
device_3.pk,
|
|
703
|
+
],
|
|
704
|
+
}
|
|
705
|
+
form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
706
|
+
with self.assertRaises(ValidationError):
|
|
707
|
+
form6.save()
|
|
708
|
+
|
|
489
709
|
|
|
490
710
|
class RelationshipAssociationTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
491
711
|
model = RelationshipAssociation
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
<link rel="icon" href="/projects/core/en/stable/assets/favicon.ico">
|
|
15
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
15
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.
|
|
23
|
+
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.76a95c52.min.css">
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/palette.06af60db.min.css">
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
<link rel="icon" href="../assets/favicon.ico">
|
|
21
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
21
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
<link rel="stylesheet" href="../assets/stylesheets/main.
|
|
29
|
+
<link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
<link rel="icon" href="../assets/favicon.ico">
|
|
19
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
19
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
<link rel="stylesheet" href="../assets/stylesheets/main.
|
|
27
|
+
<link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
|