nautobot 2.4.13__py3-none-any.whl → 2.4.14__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.
- nautobot/core/graphql/generators.py +8 -0
- nautobot/core/graphql/schema.py +30 -30
- nautobot/core/management/commands/migrate.py +90 -1
- nautobot/core/settings.yaml +3 -3
- nautobot/core/tables.py +4 -4
- nautobot/core/testing/api.py +7 -0
- nautobot/core/views/utils.py +1 -1
- nautobot/dcim/choices.py +2 -0
- nautobot/dcim/constants.py +0 -16
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/templates/dcim/rack.html +2 -318
- nautobot/dcim/templates/dcim/rack_edit.html +2 -47
- nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
- nautobot/dcim/templates/dcim/rack_update.html +47 -0
- nautobot/dcim/urls.py +2 -27
- nautobot/dcim/utils.py +4 -30
- nautobot/dcim/views.py +38 -69
- nautobot/extras/choices.py +12 -4
- nautobot/extras/filters/mixins.py +8 -6
- nautobot/extras/forms/forms.py +9 -0
- nautobot/extras/forms/mixins.py +4 -2
- nautobot/extras/migrations/0062_collect_roles_from_related_apps_roles.py +30 -7
- nautobot/extras/migrations/0124_add_joblogentry_index.py +16 -0
- nautobot/extras/models/customfields.py +52 -3
- nautobot/extras/models/jobs.py +6 -0
- nautobot/extras/models/relationships.py +55 -6
- nautobot/extras/templates/extras/graphqlquery.html +2 -97
- nautobot/extras/templates/extras/graphqlquery_list.html +1 -0
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +97 -0
- nautobot/extras/templates/extras/secretsgroup.html +2 -29
- nautobot/extras/templates/extras/secretsgroup_edit.html +2 -82
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +29 -0
- nautobot/extras/templates/extras/secretsgroup_update.html +82 -0
- nautobot/extras/tests/test_customfields.py +115 -7
- nautobot/extras/tests/test_relationships.py +7 -1
- nautobot/extras/tests/test_views.py +113 -1
- nautobot/extras/urls.py +2 -51
- nautobot/extras/utils.py +4 -1
- nautobot/extras/views.py +39 -133
- nautobot/ipam/api/views.py +69 -6
- nautobot/ipam/tests/test_api.py +350 -0
- nautobot/project-static/docs/404.html +11 -34
- nautobot/project-static/docs/apps/index.html +11 -34
- nautobot/project-static/docs/apps/nautobot-apps.html +11 -34
- nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js → bundle.50899def.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js.map → bundle.50899def.min.js.map} +2 -2
- nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css → main.7e37652d.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css.map → main.7e37652d.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +11 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +11 -34
- nautobot/project-static/docs/development/apps/api/configuration-view.html +11 -34
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -34
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +11 -34
- nautobot/project-static/docs/development/apps/api/models/global-search.html +11 -34
- nautobot/project-static/docs/development/apps/api/models/graphql.html +11 -34
- nautobot/project-static/docs/development/apps/api/models/index.html +11 -34
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +11 -34
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +11 -34
- nautobot/project-static/docs/development/apps/api/prometheus.html +11 -34
- nautobot/project-static/docs/development/apps/api/setup.html +11 -34
- nautobot/project-static/docs/development/apps/api/testing.html +11 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +11 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +11 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +11 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +11 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/base-template.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/index.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/notes.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +11 -34
- nautobot/project-static/docs/development/apps/api/views/urls.html +11 -34
- nautobot/project-static/docs/development/apps/index.html +11 -34
- nautobot/project-static/docs/development/apps/migration/code-updates.html +11 -34
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +11 -34
- nautobot/project-static/docs/development/apps/migration/from-v1.html +11 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +11 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +11 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +11 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +11 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +11 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +11 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +11 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +11 -34
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +11 -34
- nautobot/project-static/docs/development/core/application-registry.html +139 -133
- nautobot/project-static/docs/development/core/best-practices.html +11 -34
- nautobot/project-static/docs/development/core/bootstrap-ui.html +11 -34
- nautobot/project-static/docs/development/core/caching.html +11 -34
- nautobot/project-static/docs/development/core/controllers.html +11 -34
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +11 -34
- nautobot/project-static/docs/development/core/generic-views.html +11 -34
- nautobot/project-static/docs/development/core/getting-started.html +11 -34
- nautobot/project-static/docs/development/core/homepage.html +11 -34
- nautobot/project-static/docs/development/core/index.html +11 -34
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +11 -34
- nautobot/project-static/docs/development/core/model-checklist.html +11 -34
- nautobot/project-static/docs/development/core/model-features.html +11 -34
- nautobot/project-static/docs/development/core/natural-keys.html +11 -34
- nautobot/project-static/docs/development/core/navigation-menu.html +11 -34
- nautobot/project-static/docs/development/core/release-checklist.html +11 -34
- nautobot/project-static/docs/development/core/role-internals.html +11 -34
- nautobot/project-static/docs/development/core/settings.html +11 -34
- nautobot/project-static/docs/development/core/style-guide.html +11 -34
- nautobot/project-static/docs/development/core/templates.html +11 -34
- nautobot/project-static/docs/development/core/testing.html +11 -34
- nautobot/project-static/docs/development/core/ui-component-framework.html +11 -34
- nautobot/project-static/docs/development/core/user-preferences.html +11 -34
- nautobot/project-static/docs/development/index.html +11 -34
- nautobot/project-static/docs/development/jobs/getting-started.html +11 -34
- nautobot/project-static/docs/development/jobs/index.html +11 -34
- nautobot/project-static/docs/development/jobs/installation.html +11 -34
- nautobot/project-static/docs/development/jobs/job-extensions.html +11 -34
- nautobot/project-static/docs/development/jobs/job-logging.html +11 -34
- nautobot/project-static/docs/development/jobs/job-patterns.html +11 -34
- nautobot/project-static/docs/development/jobs/job-structure.html +11 -34
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +11 -34
- nautobot/project-static/docs/development/jobs/testing.html +11 -34
- nautobot/project-static/docs/index.html +11 -34
- nautobot/project-static/docs/overview/application_stack.html +11 -34
- nautobot/project-static/docs/overview/design_philosophy.html +11 -34
- nautobot/project-static/docs/release-notes/index.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.0.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.1.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.2.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.3.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.4.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.5.html +11 -34
- nautobot/project-static/docs/release-notes/version-1.6.html +11 -34
- nautobot/project-static/docs/release-notes/version-2.0.html +11 -34
- nautobot/project-static/docs/release-notes/version-2.1.html +11 -34
- nautobot/project-static/docs/release-notes/version-2.2.html +11 -34
- nautobot/project-static/docs/release-notes/version-2.3.html +11 -34
- nautobot/project-static/docs/release-notes/version-2.4.html +171 -34
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +299 -299
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +11 -34
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +11 -34
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +11 -34
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +11 -34
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +11 -34
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +14 -37
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +11 -34
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/index.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +11 -34
- nautobot/project-static/docs/user-guide/administration/installation/services.html +11 -34
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +11 -34
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +11 -34
- nautobot/project-static/docs/user-guide/administration/security/index.html +11 -34
- nautobot/project-static/docs/user-guide/administration/security/notices.html +11 -34
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +19 -39
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +11 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +14 -37
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +19 -52
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +14 -37
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +11 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +11 -34
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +11 -34
- nautobot/project-static/docs/user-guide/index.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +11 -34
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +11 -34
- nautobot/tenancy/api/views.py +2 -1
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/METADATA +5 -5
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/RECORD +356 -349
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/NOTICE +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/WHEEL +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.14.dist-info}/entry_points.txt +0 -0
|
@@ -1,82 +1,2 @@
|
|
|
1
|
-
{% extends '
|
|
2
|
-
{%
|
|
3
|
-
{% load form_helpers %}
|
|
4
|
-
|
|
5
|
-
{% block form %}
|
|
6
|
-
<div class="panel panel-default">
|
|
7
|
-
<div class="panel-heading"><strong>Secrets Group</strong></div>
|
|
8
|
-
<div class="panel-body">
|
|
9
|
-
{% render_field form.name %}
|
|
10
|
-
{% render_field form.description %}
|
|
11
|
-
</div>
|
|
12
|
-
</div>
|
|
13
|
-
<div class="panel panel-default">
|
|
14
|
-
<div class="panel-heading"><strong>Secret Assignment</strong></div>
|
|
15
|
-
<div class="panel-body">
|
|
16
|
-
{% if secrets.errors %}
|
|
17
|
-
<div class="text-danger">
|
|
18
|
-
Please correct the error(s) below:
|
|
19
|
-
|
|
20
|
-
{% for secret in secrets.forms %}
|
|
21
|
-
{% if secret.errors %}
|
|
22
|
-
{% for error in secret.errors.values %}{{ error }}{% endfor %}
|
|
23
|
-
{% endif %}
|
|
24
|
-
{% endfor %}
|
|
25
|
-
</div>
|
|
26
|
-
{% endif %}
|
|
27
|
-
{{ secrets.non_field_errors }}
|
|
28
|
-
<table class="table" id="secrets">
|
|
29
|
-
{{ secrets.management_form }}
|
|
30
|
-
{% for secret_form in secrets.forms %}
|
|
31
|
-
{% if forloop.first %}
|
|
32
|
-
<thead>
|
|
33
|
-
<tr>
|
|
34
|
-
{% for field in secret_form.visible_fields %}
|
|
35
|
-
<th>{{ field.label|capfirst }}</th>
|
|
36
|
-
{% endfor %}
|
|
37
|
-
</tr>
|
|
38
|
-
</thead>
|
|
39
|
-
{% endif %}
|
|
40
|
-
<tr class="formset_row-{{ secrets.prefix }}">
|
|
41
|
-
{% for field in secret_form.visible_fields %}
|
|
42
|
-
<td>
|
|
43
|
-
{% if forloop.first %}
|
|
44
|
-
{% for hidden in secret_form.hidden_fields %}
|
|
45
|
-
{{ hidden }}
|
|
46
|
-
{% endfor %}
|
|
47
|
-
{% endif %}
|
|
48
|
-
{{ field }}
|
|
49
|
-
{% if field.errors %}
|
|
50
|
-
<ul>
|
|
51
|
-
{% for error in field.errors %}
|
|
52
|
-
{# Embed an HTML comment indicating the error for extraction by tests #}
|
|
53
|
-
<!-- FORM-ERROR {{ field.name }}: {{ error }} -->
|
|
54
|
-
<li class="text-danger">{{ error }}</li>
|
|
55
|
-
{% endfor %}
|
|
56
|
-
</ul>
|
|
57
|
-
{% endif %}
|
|
58
|
-
</td>
|
|
59
|
-
{% endfor %}
|
|
60
|
-
</tr>
|
|
61
|
-
{% endfor %}
|
|
62
|
-
</table>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
{% include "inc/extras_features_edit_form_fields.html" %}
|
|
66
|
-
{% endblock form %}
|
|
67
|
-
|
|
68
|
-
{% block javascript %}
|
|
69
|
-
{{ block.super }}
|
|
70
|
-
<script src="{% static 'jquery/jquery.formset.js' %}"></script>
|
|
71
|
-
<script type="text/javascript">
|
|
72
|
-
$('.formset_row-{{ secrets.prefix }}').formset({
|
|
73
|
-
addText: '<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add another Secret',
|
|
74
|
-
addCssClass: 'btn btn-primary add-row',
|
|
75
|
-
deleteText: '<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span>',
|
|
76
|
-
deleteCssClass: 'btn btn-danger delete-row',
|
|
77
|
-
prefix: '{{ secrets.prefix }}',
|
|
78
|
-
formCssClass: 'dynamic-formset-{{ secrets.prefix }}',
|
|
79
|
-
added: jsify_form
|
|
80
|
-
});
|
|
81
|
-
</script>
|
|
82
|
-
{% endblock javascript %}
|
|
1
|
+
{% extends 'extras/secretsgroup_update.html' %}
|
|
2
|
+
{% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{% extends "generic/object_retrieve.html" %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block content_left_page %}
|
|
5
|
+
<div class="panel panel-default">
|
|
6
|
+
<div class="panel-heading">
|
|
7
|
+
<strong>Secrets Group</strong>
|
|
8
|
+
</div>
|
|
9
|
+
<table class="table table-hover panel-body attr-table">
|
|
10
|
+
<tr>
|
|
11
|
+
<td>Description</td>
|
|
12
|
+
<td colspan="3">{{ object.description | placeholder }}</td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td rowspan="{{ secrets_group_associations | length | add:'1'}}">Secrets</td>
|
|
16
|
+
<th>Access Type</th>
|
|
17
|
+
<th>Secret Type</th>
|
|
18
|
+
<th>Secret</th>
|
|
19
|
+
</tr>
|
|
20
|
+
{% for secret_association in secrets_group_associations %}
|
|
21
|
+
<tr>
|
|
22
|
+
<td>{{ secret_association.access_type }}</td>
|
|
23
|
+
<td>{{ secret_association.secret_type | bettertitle }}</td>
|
|
24
|
+
<td>{{ secret_association.secret|hyperlinked_object }}</td>
|
|
25
|
+
</tr>
|
|
26
|
+
{% endfor %}
|
|
27
|
+
</table>
|
|
28
|
+
</div>
|
|
29
|
+
{% endblock content_left_page %}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{% extends 'generic/object_create.html' %}
|
|
2
|
+
{% load static %}
|
|
3
|
+
{% load form_helpers %}
|
|
4
|
+
|
|
5
|
+
{% block form %}
|
|
6
|
+
<div class="panel panel-default">
|
|
7
|
+
<div class="panel-heading"><strong>Secrets Group</strong></div>
|
|
8
|
+
<div class="panel-body">
|
|
9
|
+
{% render_field form.name %}
|
|
10
|
+
{% render_field form.description %}
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="panel panel-default">
|
|
14
|
+
<div class="panel-heading"><strong>Secret Assignment</strong></div>
|
|
15
|
+
<div class="panel-body">
|
|
16
|
+
{% if secrets.errors %}
|
|
17
|
+
<div class="text-danger">
|
|
18
|
+
Please correct the error(s) below:
|
|
19
|
+
|
|
20
|
+
{% for secret in secrets.forms %}
|
|
21
|
+
{% if secret.errors %}
|
|
22
|
+
{% for error in secret.errors.values %}{{ error }}{% endfor %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
</div>
|
|
26
|
+
{% endif %}
|
|
27
|
+
{{ secrets.non_field_errors }}
|
|
28
|
+
<table class="table" id="secrets">
|
|
29
|
+
{{ secrets.management_form }}
|
|
30
|
+
{% for secret_form in secrets.forms %}
|
|
31
|
+
{% if forloop.first %}
|
|
32
|
+
<thead>
|
|
33
|
+
<tr>
|
|
34
|
+
{% for field in secret_form.visible_fields %}
|
|
35
|
+
<th>{{ field.label|capfirst }}</th>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
</tr>
|
|
38
|
+
</thead>
|
|
39
|
+
{% endif %}
|
|
40
|
+
<tr class="formset_row-{{ secrets.prefix }}">
|
|
41
|
+
{% for field in secret_form.visible_fields %}
|
|
42
|
+
<td>
|
|
43
|
+
{% if forloop.first %}
|
|
44
|
+
{% for hidden in secret_form.hidden_fields %}
|
|
45
|
+
{{ hidden }}
|
|
46
|
+
{% endfor %}
|
|
47
|
+
{% endif %}
|
|
48
|
+
{{ field }}
|
|
49
|
+
{% if field.errors %}
|
|
50
|
+
<ul>
|
|
51
|
+
{% for error in field.errors %}
|
|
52
|
+
{# Embed an HTML comment indicating the error for extraction by tests #}
|
|
53
|
+
<!-- FORM-ERROR {{ field.name }}: {{ error }} -->
|
|
54
|
+
<li class="text-danger">{{ error }}</li>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</ul>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</td>
|
|
59
|
+
{% endfor %}
|
|
60
|
+
</tr>
|
|
61
|
+
{% endfor %}
|
|
62
|
+
</table>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{% include "inc/extras_features_edit_form_fields.html" %}
|
|
66
|
+
{% endblock form %}
|
|
67
|
+
|
|
68
|
+
{% block javascript %}
|
|
69
|
+
{{ block.super }}
|
|
70
|
+
<script src="{% static 'jquery/jquery.formset.js' %}"></script>
|
|
71
|
+
<script type="text/javascript">
|
|
72
|
+
$('.formset_row-{{ secrets.prefix }}').formset({
|
|
73
|
+
addText: '<span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add another Secret',
|
|
74
|
+
addCssClass: 'btn btn-primary add-row',
|
|
75
|
+
deleteText: '<span class="mdi mdi-trash-can-outline" aria-hidden="true"></span>',
|
|
76
|
+
deleteCssClass: 'btn btn-danger delete-row',
|
|
77
|
+
prefix: '{{ secrets.prefix }}',
|
|
78
|
+
formCssClass: 'dynamic-formset-{{ secrets.prefix }}',
|
|
79
|
+
added: jsify_form
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
82
|
+
{% endblock javascript %}
|
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from django.conf import settings
|
|
5
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
6
|
from django.core.exceptions import ValidationError
|
|
7
|
-
from django.db.models import ProtectedError
|
|
7
|
+
from django.db.models import ProtectedError, QuerySet
|
|
8
8
|
from django.forms import ChoiceField, IntegerField, NumberInput
|
|
9
9
|
from django.urls import reverse
|
|
10
10
|
from rest_framework import status
|
|
@@ -400,28 +400,46 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
|
|
|
400
400
|
class CustomFieldManagerTest(TestCase):
|
|
401
401
|
def setUp(self):
|
|
402
402
|
self.content_type = ContentType.objects.get_for_model(Location)
|
|
403
|
-
custom_field = CustomField(
|
|
403
|
+
custom_field = CustomField(
|
|
404
|
+
type=CustomFieldTypeChoices.TYPE_TEXT,
|
|
405
|
+
label="Text Field",
|
|
406
|
+
default="foo",
|
|
407
|
+
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
|
|
408
|
+
)
|
|
404
409
|
custom_field.save()
|
|
405
410
|
custom_field.content_types.set([self.content_type])
|
|
406
411
|
|
|
407
412
|
def test_get_for_model(self):
|
|
408
413
|
self.assertEqual(CustomField.objects.get_for_model(Location).count(), 2)
|
|
409
414
|
self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0)
|
|
415
|
+
self.assertEqual(len(CustomField.objects.get_for_model(Location, get_queryset=False)), 2)
|
|
416
|
+
self.assertEqual(len(CustomField.objects.get_for_model(VirtualMachine, get_queryset=False)), 0)
|
|
410
417
|
|
|
411
418
|
def test_get_for_model_caching_and_cache_invalidation(self):
|
|
412
419
|
"""Test that the cache is used and is properly invalidated when CustomFields are created or deleted."""
|
|
413
420
|
# Assert that the cache is used when calling get_for_model a second time
|
|
414
421
|
CustomField.objects.get_for_model(Location)
|
|
415
422
|
with self.assertNumQueries(0):
|
|
416
|
-
CustomField.objects.get_for_model(Location)
|
|
423
|
+
qs = CustomField.objects.get_for_model(Location)
|
|
424
|
+
with self.assertNumQueries(0):
|
|
425
|
+
listing = CustomField.objects.get_for_model(Location, get_queryset=False)
|
|
426
|
+
self.assertIsInstance(qs, QuerySet)
|
|
427
|
+
self.assertIsInstance(listing, list)
|
|
428
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
417
429
|
|
|
418
430
|
# Assert that different values of exclude_filter_disabled are cached separately
|
|
419
431
|
with self.assertNumQueries(1):
|
|
420
432
|
CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
|
|
421
433
|
with self.assertNumQueries(0):
|
|
422
|
-
CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
|
|
434
|
+
qs = CustomField.objects.get_for_model(Location, exclude_filter_disabled=True)
|
|
435
|
+
with self.assertNumQueries(0):
|
|
436
|
+
listing = CustomField.objects.get_for_model(Location, exclude_filter_disabled=True, get_queryset=False)
|
|
423
437
|
with self.assertNumQueries(0):
|
|
424
438
|
CustomField.objects.get_for_model(Location)
|
|
439
|
+
self.assertIsInstance(qs, QuerySet)
|
|
440
|
+
self.assertIsInstance(listing, list)
|
|
441
|
+
self.assertEqual(1, len(listing))
|
|
442
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
425
443
|
|
|
426
444
|
# Assert that different models are cached separately
|
|
427
445
|
with self.assertNumQueries(1):
|
|
@@ -444,21 +462,111 @@ class CustomFieldManagerTest(TestCase):
|
|
|
444
462
|
with self.assertNumQueries(1):
|
|
445
463
|
CustomField.objects.get_for_model(Location)
|
|
446
464
|
with self.assertNumQueries(0):
|
|
447
|
-
CustomField.objects.get_for_model(Location)
|
|
465
|
+
qs = CustomField.objects.get_for_model(Location)
|
|
466
|
+
with self.assertNumQueries(0):
|
|
467
|
+
listing = CustomField.objects.get_for_model(Location, get_queryset=False)
|
|
468
|
+
self.assertIsInstance(qs, QuerySet)
|
|
469
|
+
self.assertIsInstance(listing, list)
|
|
470
|
+
self.assertEqual(3, len(listing))
|
|
471
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
448
472
|
|
|
449
473
|
# Assert that the cache is invalidated when removing a CustomField.content_types m2m relationship
|
|
450
474
|
custom_field.content_types.set([])
|
|
451
475
|
with self.assertNumQueries(1):
|
|
452
476
|
CustomField.objects.get_for_model(Location)
|
|
453
477
|
with self.assertNumQueries(0):
|
|
454
|
-
CustomField.objects.get_for_model(Location)
|
|
478
|
+
qs = CustomField.objects.get_for_model(Location)
|
|
479
|
+
with self.assertNumQueries(0):
|
|
480
|
+
listing = CustomField.objects.get_for_model(Location, get_queryset=False)
|
|
481
|
+
self.assertIsInstance(qs, QuerySet)
|
|
482
|
+
self.assertIsInstance(listing, list)
|
|
483
|
+
self.assertEqual(2, len(listing))
|
|
484
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
455
485
|
|
|
456
486
|
# Assert that the cache is invalidated on object delete
|
|
457
487
|
custom_field.delete()
|
|
458
488
|
with self.assertNumQueries(1):
|
|
459
489
|
CustomField.objects.get_for_model(Location)
|
|
460
490
|
with self.assertNumQueries(0):
|
|
461
|
-
CustomField.objects.get_for_model(Location)
|
|
491
|
+
qs = CustomField.objects.get_for_model(Location)
|
|
492
|
+
with self.assertNumQueries(0):
|
|
493
|
+
listing = CustomField.objects.get_for_model(Location, get_queryset=False)
|
|
494
|
+
self.assertIsInstance(qs, QuerySet)
|
|
495
|
+
self.assertIsInstance(listing, list)
|
|
496
|
+
self.assertEqual(2, len(listing))
|
|
497
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class ComputedFieldManagerTestCase(TestCase):
|
|
501
|
+
def setUp(self):
|
|
502
|
+
self.content_type = ContentType.objects.get_for_model(Location)
|
|
503
|
+
ComputedField.objects.create(
|
|
504
|
+
content_type=ContentType.objects.get_for_model(Location),
|
|
505
|
+
key="computed_field_one",
|
|
506
|
+
label="Computed Field One",
|
|
507
|
+
template="{{ obj.name }} is the name of this location.",
|
|
508
|
+
fallback_value="An error occurred while rendering this template.",
|
|
509
|
+
weight=100,
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def test_get_for_model(self):
|
|
513
|
+
self.assertEqual(ComputedField.objects.get_for_model(Location).count(), 1)
|
|
514
|
+
self.assertEqual(ComputedField.objects.get_for_model(VirtualMachine).count(), 0)
|
|
515
|
+
self.assertEqual(len(ComputedField.objects.get_for_model(Location, get_queryset=False)), 1)
|
|
516
|
+
self.assertEqual(len(ComputedField.objects.get_for_model(VirtualMachine, get_queryset=False)), 0)
|
|
517
|
+
|
|
518
|
+
def test_get_for_model_caching_and_cache_invalidation(self):
|
|
519
|
+
# Assert that the cache is used when calling get_for_model a second time
|
|
520
|
+
ComputedField.objects.get_for_model(Location)
|
|
521
|
+
with self.assertNumQueries(0):
|
|
522
|
+
qs = ComputedField.objects.get_for_model(Location)
|
|
523
|
+
with self.assertNumQueries(0):
|
|
524
|
+
listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
|
|
525
|
+
self.assertIsInstance(qs, QuerySet)
|
|
526
|
+
self.assertIsInstance(listing, list)
|
|
527
|
+
self.assertEqual(1, len(listing))
|
|
528
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
529
|
+
|
|
530
|
+
# Assert that different models are cached separately
|
|
531
|
+
with self.assertNumQueries(1):
|
|
532
|
+
ComputedField.objects.get_for_model(VirtualMachine)
|
|
533
|
+
with self.assertNumQueries(0):
|
|
534
|
+
ComputedField.objects.get_for_model(VirtualMachine)
|
|
535
|
+
with self.assertNumQueries(0):
|
|
536
|
+
ComputedField.objects.get_for_model(Location)
|
|
537
|
+
|
|
538
|
+
# Assert that the cache is invalidated on object save
|
|
539
|
+
computed_field = ComputedField.objects.create(
|
|
540
|
+
content_type=ContentType.objects.get_for_model(Location),
|
|
541
|
+
key="computed_field_two",
|
|
542
|
+
label="Computed Field Two",
|
|
543
|
+
template="{{ obj.name }} is still jthe name of this location.",
|
|
544
|
+
fallback_value="An error occurred while rendering this template.",
|
|
545
|
+
weight=200,
|
|
546
|
+
)
|
|
547
|
+
with self.assertNumQueries(1):
|
|
548
|
+
ComputedField.objects.get_for_model(Location)
|
|
549
|
+
with self.assertNumQueries(0):
|
|
550
|
+
qs = ComputedField.objects.get_for_model(Location)
|
|
551
|
+
with self.assertNumQueries(0):
|
|
552
|
+
listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
|
|
553
|
+
self.assertIsInstance(qs, QuerySet)
|
|
554
|
+
self.assertIsInstance(listing, list)
|
|
555
|
+
self.assertEqual(2, len(listing))
|
|
556
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
557
|
+
|
|
558
|
+
# Assert that the cache is invalided on object delete
|
|
559
|
+
computed_field.delete()
|
|
560
|
+
with self.assertNumQueries(1):
|
|
561
|
+
ComputedField.objects.get_for_model(Location)
|
|
562
|
+
with self.assertNumQueries(0):
|
|
563
|
+
qs = ComputedField.objects.get_for_model(Location)
|
|
564
|
+
with self.assertNumQueries(0):
|
|
565
|
+
listing = ComputedField.objects.get_for_model(Location, get_queryset=False)
|
|
566
|
+
self.assertIsInstance(qs, QuerySet)
|
|
567
|
+
self.assertIsInstance(listing, list)
|
|
568
|
+
self.assertEqual(1, len(listing))
|
|
569
|
+
self.assertQuerysetEqualAndNotEmpty(qs, listing)
|
|
462
570
|
|
|
463
571
|
|
|
464
572
|
class CustomFieldDataAPITest(APITestCase):
|
|
@@ -482,10 +482,12 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
482
482
|
# Assert that the cache is used when calling method a second time
|
|
483
483
|
with self.assertNumQueries(0):
|
|
484
484
|
manager_method(Location)
|
|
485
|
+
with self.assertNumQueries(0):
|
|
486
|
+
manager_method(Location, get_queryset=False)
|
|
485
487
|
|
|
486
488
|
# Assert that different models are cached separately
|
|
487
489
|
with self.assertNumQueries(expected_queries):
|
|
488
|
-
manager_method(Rack)
|
|
490
|
+
manager_method(Rack, get_queryset=False)
|
|
489
491
|
with self.assertNumQueries(0):
|
|
490
492
|
manager_method(Rack)
|
|
491
493
|
with self.assertNumQueries(0):
|
|
@@ -507,6 +509,8 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
507
509
|
manager_method(Location)
|
|
508
510
|
with self.assertNumQueries(0):
|
|
509
511
|
manager_method(Location)
|
|
512
|
+
with self.assertNumQueries(0):
|
|
513
|
+
manager_method(Location, get_queryset=False)
|
|
510
514
|
finally:
|
|
511
515
|
# Assert that the cache is invalidated on object delete
|
|
512
516
|
relationship.delete()
|
|
@@ -514,6 +518,8 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
514
518
|
manager_method(Location)
|
|
515
519
|
with self.assertNumQueries(0):
|
|
516
520
|
manager_method(Location)
|
|
521
|
+
with self.assertNumQueries(0):
|
|
522
|
+
manager_method(Location, get_queryset=False)
|
|
517
523
|
|
|
518
524
|
def test_required_related_object_errors(self):
|
|
519
525
|
"""
|
|
@@ -1850,8 +1850,18 @@ class SecretTestCase(
|
|
|
1850
1850
|
}
|
|
1851
1851
|
|
|
1852
1852
|
|
|
1853
|
-
class SecretsGroupTestCase(
|
|
1853
|
+
class SecretsGroupTestCase(
|
|
1854
|
+
ViewTestCases.OrganizationalObjectViewTestCase,
|
|
1855
|
+
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
1856
|
+
):
|
|
1854
1857
|
model = SecretsGroup
|
|
1858
|
+
custom_test_permissions = [
|
|
1859
|
+
"extras.view_secret",
|
|
1860
|
+
"extras.add_secretsgroup",
|
|
1861
|
+
"extras.view_secretsgroup",
|
|
1862
|
+
"extras.add_secretsgroupassociation",
|
|
1863
|
+
"extras.change_secretsgroupassociation",
|
|
1864
|
+
]
|
|
1855
1865
|
|
|
1856
1866
|
@classmethod
|
|
1857
1867
|
def setUpTestData(cls):
|
|
@@ -1895,6 +1905,108 @@ class SecretsGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
1895
1905
|
"secrets_group_associations-MIN_NUM_FORMS": "0",
|
|
1896
1906
|
"secrets_group_associations-MAX_NUM_FORMS": "1000",
|
|
1897
1907
|
}
|
|
1908
|
+
cls.bulk_edit_data = {
|
|
1909
|
+
"description": "This is a very detailed new description",
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
def test_create_group_with_valid_secret_association(self):
|
|
1913
|
+
"""Test that a SecretsGroup with a valid Secret association saves correctly via the formset."""
|
|
1914
|
+
self.add_permissions(*self.custom_test_permissions)
|
|
1915
|
+
# Create a secret to associate
|
|
1916
|
+
secret = Secret.objects.create(
|
|
1917
|
+
name="AWS_Secret",
|
|
1918
|
+
provider="text-file",
|
|
1919
|
+
parameters={"path": "/tmp"}, # noqa: S108 # hardcoded-temp-file -- false positive
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
form_data = {
|
|
1923
|
+
"name": "test",
|
|
1924
|
+
"description": "test bulk edits",
|
|
1925
|
+
"secrets_group_associations-TOTAL_FORMS": "1",
|
|
1926
|
+
"secrets_group_associations-INITIAL_FORMS": "0",
|
|
1927
|
+
"secrets_group_associations-MIN_NUM_FORMS": "0",
|
|
1928
|
+
"secrets_group_associations-MAX_NUM_FORMS": "1000",
|
|
1929
|
+
"secrets_group_associations-0-secret": secret.pk,
|
|
1930
|
+
"secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
|
|
1931
|
+
"secrets_group_associations-0-secret_type": SecretsGroupSecretTypeChoices.TYPE_PASSWORD,
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
# Submit the form to the "add SecretsGroup" view
|
|
1935
|
+
response = self.client.post(reverse("extras:secretsgroup_add"), data=form_data, follow=True)
|
|
1936
|
+
|
|
1937
|
+
self.assertEqual(response.status_code, 200)
|
|
1938
|
+
self.assertTrue(SecretsGroup.objects.filter(name="test").exists())
|
|
1939
|
+
|
|
1940
|
+
# Checks that the association was created correctly
|
|
1941
|
+
group = SecretsGroup.objects.get(name="test")
|
|
1942
|
+
self.assertEqual(group.secrets_group_associations.count(), 1)
|
|
1943
|
+
|
|
1944
|
+
association = group.secrets_group_associations.first()
|
|
1945
|
+
self.assertEqual(association.secret, secret)
|
|
1946
|
+
self.assertEqual(association.access_type, SecretsGroupAccessTypeChoices.TYPE_HTTP)
|
|
1947
|
+
self.assertEqual(association.secret_type, SecretsGroupSecretTypeChoices.TYPE_PASSWORD)
|
|
1948
|
+
|
|
1949
|
+
def test_create_group_with_invalid_secret_association(self):
|
|
1950
|
+
"""Test that invalid Secret association formset raises validation error and does not save."""
|
|
1951
|
+
self.add_permissions(*self.custom_test_permissions)
|
|
1952
|
+
url = reverse("extras:secretsgroup_add")
|
|
1953
|
+
|
|
1954
|
+
form_data = {
|
|
1955
|
+
"name": "Invalid Secrets Group",
|
|
1956
|
+
"description": "Missing required fields",
|
|
1957
|
+
"secrets_group_associations-TOTAL_FORMS": "1",
|
|
1958
|
+
"secrets_group_associations-INITIAL_FORMS": "0",
|
|
1959
|
+
"secrets_group_associations-MIN_NUM_FORMS": "0",
|
|
1960
|
+
"secrets_group_associations-MAX_NUM_FORMS": "1000",
|
|
1961
|
+
"secrets_group_associations-0-secret": "", # invalid
|
|
1962
|
+
"secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
|
|
1963
|
+
"secrets_group_associations-0-secret_type": "", # invalid
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
response = self.client.post(url, data=form_data)
|
|
1967
|
+
|
|
1968
|
+
self.assertEqual(response.status_code, 200)
|
|
1969
|
+
|
|
1970
|
+
# Checks that no new SecretsGroup was created
|
|
1971
|
+
self.assertFalse(SecretsGroup.objects.filter(name="Invalid Secrets Group").exists())
|
|
1972
|
+
|
|
1973
|
+
# Checks that formset errors are raised in the context
|
|
1974
|
+
self.assertFormsetError(
|
|
1975
|
+
response.context["secrets"], form_index=0, field="secret", errors=["This field is required."]
|
|
1976
|
+
)
|
|
1977
|
+
|
|
1978
|
+
def test_create_group_with_deleted_secret_fails_cleanly(self):
|
|
1979
|
+
"""
|
|
1980
|
+
Creating a SecretsGroup with a deleted Secret should fail with a formset error.
|
|
1981
|
+
"""
|
|
1982
|
+
self.add_permissions(*self.custom_test_permissions)
|
|
1983
|
+
|
|
1984
|
+
secret = Secret.objects.create(name="TempSecret", provider="text-file", parameters={"path": "/tmp"}) # noqa: S108 # hardcoded-temp-file -- false positive
|
|
1985
|
+
secret_pk = secret.pk
|
|
1986
|
+
secret.delete()
|
|
1987
|
+
|
|
1988
|
+
form_data = {
|
|
1989
|
+
"name": "Test Group",
|
|
1990
|
+
"description": "This should not be created",
|
|
1991
|
+
"secrets_group_associations-TOTAL_FORMS": "1",
|
|
1992
|
+
"secrets_group_associations-INITIAL_FORMS": "0",
|
|
1993
|
+
"secrets_group_associations-MIN_NUM_FORMS": "0",
|
|
1994
|
+
"secrets_group_associations-MAX_NUM_FORMS": "1000",
|
|
1995
|
+
"secrets_group_associations-0-secret": secret_pk,
|
|
1996
|
+
"secrets_group_associations-0-access_type": SecretsGroupAccessTypeChoices.TYPE_HTTP,
|
|
1997
|
+
"secrets_group_associations-0-secret_type": SecretsGroupSecretTypeChoices.TYPE_PASSWORD,
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
response = self.client.post(reverse("extras:secretsgroup_add"), data=form_data)
|
|
2001
|
+
self.assertEqual(response.status_code, 200)
|
|
2002
|
+
|
|
2003
|
+
self.assertFormsetError(
|
|
2004
|
+
response.context["secrets"],
|
|
2005
|
+
form_index=0,
|
|
2006
|
+
field="secret",
|
|
2007
|
+
errors=["Select a valid choice. That choice is not one of the available choices."],
|
|
2008
|
+
)
|
|
2009
|
+
self.assertFalse(SecretsGroup.objects.filter(name="Test Group").exists())
|
|
1898
2010
|
|
|
1899
2011
|
|
|
1900
2012
|
class GraphQLQueriesTestCase(
|
nautobot/extras/urls.py
CHANGED
|
@@ -6,11 +6,9 @@ from nautobot.extras.models import (
|
|
|
6
6
|
CustomField,
|
|
7
7
|
DynamicGroup,
|
|
8
8
|
GitRepository,
|
|
9
|
-
GraphQLQuery,
|
|
10
9
|
Job,
|
|
11
10
|
Note,
|
|
12
11
|
Relationship,
|
|
13
|
-
SecretsGroup,
|
|
14
12
|
)
|
|
15
13
|
|
|
16
14
|
app_name = "extras"
|
|
@@ -24,6 +22,7 @@ router.register("contact-associations", views.ContactAssociationUIViewSet)
|
|
|
24
22
|
router.register("custom-links", views.CustomLinkUIViewSet)
|
|
25
23
|
router.register("export-templates", views.ExportTemplateUIViewSet)
|
|
26
24
|
router.register("external-integrations", views.ExternalIntegrationUIViewSet)
|
|
25
|
+
router.register("graphql-queries", views.GraphQLQueryUIViewSet)
|
|
27
26
|
router.register("job-buttons", views.JobButtonUIViewSet)
|
|
28
27
|
router.register("job-hooks", views.JobHookUIViewSet)
|
|
29
28
|
router.register("job-queues", views.JobQueueUIViewSet)
|
|
@@ -34,6 +33,7 @@ router.register("relationships", views.RelationshipUIViewSet)
|
|
|
34
33
|
router.register("roles", views.RoleUIViewSet)
|
|
35
34
|
router.register("saved-views", views.SavedViewUIViewSet)
|
|
36
35
|
router.register("secrets", views.SecretUIViewSet)
|
|
36
|
+
router.register("secrets-groups", views.SecretsGroupUIViewSet)
|
|
37
37
|
router.register("static-group-associations", views.StaticGroupAssociationUIViewSet)
|
|
38
38
|
router.register("statuses", views.StatusUIViewSet)
|
|
39
39
|
router.register("tags", views.TagUIViewSet)
|
|
@@ -181,37 +181,6 @@ urlpatterns = [
|
|
|
181
181
|
views.GitRepositoryDryRunView.as_view(),
|
|
182
182
|
name="gitrepository_dryrun",
|
|
183
183
|
),
|
|
184
|
-
# GraphQL Queries
|
|
185
|
-
path("graphql-queries/", views.GraphQLQueryListView.as_view(), name="graphqlquery_list"),
|
|
186
|
-
path("graphql-queries/add/", views.GraphQLQueryEditView.as_view(), name="graphqlquery_add"),
|
|
187
|
-
path(
|
|
188
|
-
"graphql-queries/delete/",
|
|
189
|
-
views.GraphQLQueryBulkDeleteView.as_view(),
|
|
190
|
-
name="GraphQLQuery_bulk_delete",
|
|
191
|
-
),
|
|
192
|
-
path("graphql-queries/<uuid:pk>/", views.GraphQLQueryView.as_view(), name="graphqlquery"),
|
|
193
|
-
path(
|
|
194
|
-
"graphql-queries/<uuid:pk>/edit/",
|
|
195
|
-
views.GraphQLQueryEditView.as_view(),
|
|
196
|
-
name="graphqlquery_edit",
|
|
197
|
-
),
|
|
198
|
-
path(
|
|
199
|
-
"graphql-queries/<uuid:pk>/delete/",
|
|
200
|
-
views.GraphQLQueryDeleteView.as_view(),
|
|
201
|
-
name="graphqlquery_delete",
|
|
202
|
-
),
|
|
203
|
-
path(
|
|
204
|
-
"graphql-queries/<uuid:pk>/changelog/",
|
|
205
|
-
views.ObjectChangeLogView.as_view(),
|
|
206
|
-
name="graphqlquery_changelog",
|
|
207
|
-
kwargs={"model": GraphQLQuery},
|
|
208
|
-
),
|
|
209
|
-
path(
|
|
210
|
-
"graphql-queries/<uuid:pk>/notes/",
|
|
211
|
-
views.ObjectNotesView.as_view(),
|
|
212
|
-
name="graphqlquery_notes",
|
|
213
|
-
kwargs={"model": GraphQLQuery},
|
|
214
|
-
),
|
|
215
184
|
# Image attachments
|
|
216
185
|
path(
|
|
217
186
|
"image-attachments/<uuid:pk>/edit/",
|
|
@@ -311,24 +280,6 @@ urlpatterns = [
|
|
|
311
280
|
views.SecretProviderParametersFormView.as_view(),
|
|
312
281
|
name="secret_provider_parameters_form",
|
|
313
282
|
),
|
|
314
|
-
path("secrets-groups/", views.SecretsGroupListView.as_view(), name="secretsgroup_list"),
|
|
315
|
-
path("secrets-groups/add/", views.SecretsGroupEditView.as_view(), name="secretsgroup_add"),
|
|
316
|
-
path("secrets-groups/delete/", views.SecretsGroupBulkDeleteView.as_view(), name="secretsgroup_bulk_delete"),
|
|
317
|
-
path("secrets-groups/<uuid:pk>/", views.SecretsGroupView.as_view(), name="secretsgroup"),
|
|
318
|
-
path("secrets-groups/<uuid:pk>/edit/", views.SecretsGroupEditView.as_view(), name="secretsgroup_edit"),
|
|
319
|
-
path("secrets-groups/<uuid:pk>/delete/", views.SecretsGroupDeleteView.as_view(), name="secretsgroup_delete"),
|
|
320
|
-
path(
|
|
321
|
-
"secrets-groups/<uuid:pk>/changelog/",
|
|
322
|
-
views.ObjectChangeLogView.as_view(),
|
|
323
|
-
name="secretsgroup_changelog",
|
|
324
|
-
kwargs={"model": SecretsGroup},
|
|
325
|
-
),
|
|
326
|
-
path(
|
|
327
|
-
"secrets-groups/<uuid:pk>/notes/",
|
|
328
|
-
views.ObjectNotesView.as_view(),
|
|
329
|
-
name="secretsgroup_notes",
|
|
330
|
-
kwargs={"model": SecretsGroup},
|
|
331
|
-
),
|
|
332
283
|
]
|
|
333
284
|
|
|
334
285
|
urlpatterns += router.urls
|
nautobot/extras/utils.py
CHANGED
|
@@ -275,11 +275,14 @@ def extras_features(*features):
|
|
|
275
275
|
"""
|
|
276
276
|
|
|
277
277
|
def wrapper(model_class):
|
|
278
|
-
# Initialize the model_features
|
|
278
|
+
# Initialize the model_features and feature_models stores if not already defined
|
|
279
279
|
if "model_features" not in registry:
|
|
280
280
|
registry["model_features"] = {f: collections.defaultdict(list) for f in EXTRAS_FEATURES}
|
|
281
|
+
if "feature_models" not in registry:
|
|
282
|
+
registry["feature_models"] = {f: [] for f in EXTRAS_FEATURES}
|
|
281
283
|
for feature in features:
|
|
282
284
|
if feature in EXTRAS_FEATURES:
|
|
285
|
+
registry["feature_models"][feature].append(model_class)
|
|
283
286
|
app_label, model_name = model_class._meta.label_lower.split(".")
|
|
284
287
|
registry["model_features"][feature][app_label].append(model_name)
|
|
285
288
|
else:
|