nautobot 2.4.12__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/templates/inc/footer.html +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/migrations/0071_alter_consoleport_options_and_more.py +42 -0
- nautobot/dcim/models/device_components.py +10 -2
- nautobot/dcim/models/devices.py +18 -0
- nautobot/dcim/templates/dcim/device.html +1 -34
- nautobot/dcim/templates/dcim/rack.html +2 -318
- nautobot/dcim/templates/dcim/rack_edit.html +2 -47
- nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -1
- nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
- nautobot/dcim/templates/dcim/rack_update.html +47 -0
- nautobot/dcim/tests/test_models.py +1 -0
- nautobot/dcim/urls.py +3 -28
- nautobot/dcim/utils.py +4 -30
- nautobot/dcim/views.py +73 -71
- 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 +42 -135
- nautobot/ipam/api/views.py +69 -6
- nautobot/ipam/migrations/0052_alter_ipaddress_index_together_and_more.py +28 -0
- nautobot/ipam/models.py +13 -1
- nautobot/ipam/tests/test_api.py +351 -3
- nautobot/ipam/utils/testing.py +76 -29
- 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 +13 -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 +271 -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.12.dist-info → nautobot-2.4.14.dist-info}/METADATA +5 -5
- {nautobot-2.4.12.dist-info → nautobot-2.4.14.dist-info}/RECORD +366 -357
- {nautobot-2.4.12.dist-info → nautobot-2.4.14.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.12.dist-info → nautobot-2.4.14.dist-info}/NOTICE +0 -0
- {nautobot-2.4.12.dist-info → nautobot-2.4.14.dist-info}/WHEEL +0 -0
- {nautobot-2.4.12.dist-info → nautobot-2.4.14.dist-info}/entry_points.txt +0 -0
|
@@ -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:
|
nautobot/extras/views.py
CHANGED
|
@@ -945,11 +945,13 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
945
945
|
filterset = filters.DynamicGroupFilterSet
|
|
946
946
|
|
|
947
947
|
|
|
948
|
-
# 3.0 TODO: remove, deprecated since 2.3 (#5845)
|
|
949
948
|
class ObjectDynamicGroupsView(generic.GenericView):
|
|
950
949
|
"""
|
|
951
950
|
Present a list of dynamic groups associated to a particular object.
|
|
952
951
|
|
|
952
|
+
Note that this isn't currently widely used, as most object detail views currently render the table inline
|
|
953
|
+
rather than using this separate view. This may change in the future.
|
|
954
|
+
|
|
953
955
|
base_template: Specify to explicitly identify the base object detail template to render.
|
|
954
956
|
If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
|
|
955
957
|
will be used, as per `get_base_template()`.
|
|
@@ -969,7 +971,6 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
969
971
|
data=obj.dynamic_groups.restrict(request.user, "view"), orderable=False
|
|
970
972
|
)
|
|
971
973
|
dynamicgroups_table.columns.hide("content_type")
|
|
972
|
-
dynamicgroups_table.columns.hide("members")
|
|
973
974
|
|
|
974
975
|
# Apply the request context
|
|
975
976
|
paginate = {
|
|
@@ -1229,32 +1230,24 @@ class GitRepositoryResultView(generic.ObjectView):
|
|
|
1229
1230
|
#
|
|
1230
1231
|
|
|
1231
1232
|
|
|
1232
|
-
class
|
|
1233
|
+
class GraphQLQueryUIViewSet(
|
|
1234
|
+
ObjectDetailViewMixin,
|
|
1235
|
+
ObjectListViewMixin,
|
|
1236
|
+
ObjectEditViewMixin,
|
|
1237
|
+
ObjectDestroyViewMixin,
|
|
1238
|
+
ObjectBulkDestroyViewMixin,
|
|
1239
|
+
ObjectChangeLogViewMixin,
|
|
1240
|
+
ObjectNotesViewMixin,
|
|
1241
|
+
):
|
|
1242
|
+
filterset_form_class = forms.GraphQLQueryFilterForm
|
|
1233
1243
|
queryset = GraphQLQuery.objects.all()
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1244
|
+
form_class = forms.GraphQLQueryForm
|
|
1245
|
+
filterset_class = filters.GraphQLQueryFilterSet
|
|
1246
|
+
serializer_class = serializers.GraphQLQuerySerializer
|
|
1247
|
+
table_class = tables.GraphQLQueryTable
|
|
1237
1248
|
action_buttons = ("add",)
|
|
1238
1249
|
|
|
1239
1250
|
|
|
1240
|
-
class GraphQLQueryView(generic.ObjectView):
|
|
1241
|
-
queryset = GraphQLQuery.objects.all()
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
class GraphQLQueryEditView(generic.ObjectEditView):
|
|
1245
|
-
queryset = GraphQLQuery.objects.all()
|
|
1246
|
-
model_form = forms.GraphQLQueryForm
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
class GraphQLQueryDeleteView(generic.ObjectDeleteView):
|
|
1250
|
-
queryset = GraphQLQuery.objects.all()
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
class GraphQLQueryBulkDeleteView(generic.BulkDeleteView):
|
|
1254
|
-
queryset = GraphQLQuery.objects.all()
|
|
1255
|
-
table = tables.GraphQLQueryTable
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
1251
|
#
|
|
1259
1252
|
# Image attachments
|
|
1260
1253
|
#
|
|
@@ -2744,123 +2737,37 @@ class SecretProviderParametersFormView(generic.GenericView):
|
|
|
2744
2737
|
)
|
|
2745
2738
|
|
|
2746
2739
|
|
|
2747
|
-
class
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
class SecretsGroupView(generic.ObjectView):
|
|
2756
|
-
queryset = SecretsGroup.objects.all()
|
|
2757
|
-
|
|
2758
|
-
def get_extra_context(self, request, instance):
|
|
2759
|
-
return {"secrets_group_associations": SecretsGroupAssociation.objects.filter(secrets_group=instance)}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
class SecretsGroupEditView(generic.ObjectEditView):
|
|
2740
|
+
class SecretsGroupUIViewSet(NautobotUIViewSet):
|
|
2741
|
+
bulk_update_form_class = forms.SecretsGroupBulkEditForm
|
|
2742
|
+
filterset_class = filters.SecretsGroupFilterSet
|
|
2743
|
+
filterset_form_class = forms.SecretsGroupFilterForm
|
|
2744
|
+
form_class = forms.SecretsGroupForm
|
|
2745
|
+
serializer_class = serializers.SecretsGroupSerializer
|
|
2746
|
+
table_class = tables.SecretsGroupTable
|
|
2747
|
+
template_name = "extras/secretsgroup_update.html"
|
|
2763
2748
|
queryset = SecretsGroup.objects.all()
|
|
2764
|
-
model_form = forms.SecretsGroupForm
|
|
2765
|
-
template_name = "extras/secretsgroup_edit.html"
|
|
2766
|
-
|
|
2767
|
-
def get_extra_context(self, request, instance):
|
|
2768
|
-
ctx = super().get_extra_context(request, instance)
|
|
2769
|
-
|
|
2770
|
-
if request.POST:
|
|
2771
|
-
ctx["secrets"] = forms.SecretsGroupAssociationFormSet(data=request.POST, instance=instance)
|
|
2772
|
-
else:
|
|
2773
|
-
ctx["secrets"] = forms.SecretsGroupAssociationFormSet(instance=instance)
|
|
2774
|
-
|
|
2775
|
-
return ctx
|
|
2776
|
-
|
|
2777
|
-
def post(self, request, *args, **kwargs):
|
|
2778
|
-
obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
|
|
2779
|
-
form = self.model_form(data=request.POST, files=request.FILES, instance=obj)
|
|
2780
|
-
restrict_form_fields(form, request.user)
|
|
2781
|
-
|
|
2782
|
-
if form.is_valid():
|
|
2783
|
-
logger.debug("Form validation was successful")
|
|
2784
|
-
|
|
2785
|
-
try:
|
|
2786
|
-
with transaction.atomic():
|
|
2787
|
-
object_created = not form.instance.present_in_database
|
|
2788
|
-
obj = form.save()
|
|
2789
|
-
|
|
2790
|
-
# Check that the new object conforms with any assigned object-level permissions
|
|
2791
|
-
self.queryset.get(pk=obj.pk)
|
|
2792
|
-
|
|
2793
|
-
# Process the formsets for secrets
|
|
2794
|
-
ctx = self.get_extra_context(request, obj)
|
|
2795
|
-
secrets = ctx["secrets"]
|
|
2796
|
-
if secrets.is_valid():
|
|
2797
|
-
secrets.save()
|
|
2798
|
-
else:
|
|
2799
|
-
raise RuntimeError(secrets.errors)
|
|
2800
|
-
verb = "Created" if object_created else "Modified"
|
|
2801
|
-
msg = f"{verb} {self.queryset.model._meta.verbose_name}"
|
|
2802
|
-
logger.info(f"{msg} {obj} (PK: {obj.pk})")
|
|
2803
|
-
try:
|
|
2804
|
-
msg = format_html('{} <a href="{}">{}</a>', msg, obj.get_absolute_url(), obj)
|
|
2805
|
-
except AttributeError:
|
|
2806
|
-
msg = format_html("{} {}", msg, obj)
|
|
2807
|
-
messages.success(request, msg)
|
|
2808
2749
|
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
return redirect(iri_to_uri(return_url))
|
|
2820
|
-
else:
|
|
2821
|
-
return redirect(self.get_return_url(request, obj))
|
|
2750
|
+
def get_extra_context(self, request, instance=None):
|
|
2751
|
+
context = super().get_extra_context(request, instance)
|
|
2752
|
+
if self.action == "retrieve" and instance:
|
|
2753
|
+
context["secrets_group_associations"] = SecretsGroupAssociation.objects.filter(secrets_group=instance)
|
|
2754
|
+
if self.action in ("create", "update"):
|
|
2755
|
+
if request.method == "POST":
|
|
2756
|
+
context["secrets"] = forms.SecretsGroupAssociationFormSet(data=request.POST, instance=instance)
|
|
2757
|
+
else:
|
|
2758
|
+
context["secrets"] = forms.SecretsGroupAssociationFormSet(instance=instance)
|
|
2759
|
+
return context
|
|
2822
2760
|
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
form.add_error(None, msg)
|
|
2827
|
-
except RuntimeError:
|
|
2828
|
-
msg = "Errors encountered when saving secrets group associations. See below."
|
|
2829
|
-
logger.debug(msg)
|
|
2830
|
-
form.add_error(None, msg)
|
|
2831
|
-
except ProtectedError as err:
|
|
2832
|
-
# e.g. Trying to delete a choice that is in use.
|
|
2833
|
-
err_msg = err.args[0]
|
|
2834
|
-
protected_obj = err.protected_objects[0]
|
|
2835
|
-
msg = f"{protected_obj.value}: {err_msg} Please cancel this edit and start again."
|
|
2836
|
-
logger.debug(msg)
|
|
2837
|
-
form.add_error(None, msg)
|
|
2761
|
+
def form_save(self, form, **kwargs):
|
|
2762
|
+
obj = super().form_save(form, **kwargs)
|
|
2763
|
+
secrets = forms.SecretsGroupAssociationFormSet(data=self.request.POST, instance=form.instance)
|
|
2838
2764
|
|
|
2765
|
+
if secrets.is_valid():
|
|
2766
|
+
secrets.save()
|
|
2839
2767
|
else:
|
|
2840
|
-
|
|
2768
|
+
raise ValidationError(secrets.errors)
|
|
2841
2769
|
|
|
2842
|
-
return
|
|
2843
|
-
request,
|
|
2844
|
-
self.template_name,
|
|
2845
|
-
{
|
|
2846
|
-
"obj": obj,
|
|
2847
|
-
"obj_type": self.queryset.model._meta.verbose_name,
|
|
2848
|
-
"form": form,
|
|
2849
|
-
"return_url": self.get_return_url(request, obj),
|
|
2850
|
-
"editing": obj.present_in_database,
|
|
2851
|
-
**self.get_extra_context(request, obj),
|
|
2852
|
-
},
|
|
2853
|
-
)
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
class SecretsGroupDeleteView(generic.ObjectDeleteView):
|
|
2857
|
-
queryset = SecretsGroup.objects.all()
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
|
|
2861
|
-
queryset = SecretsGroup.objects.all()
|
|
2862
|
-
filterset = filters.SecretsGroupFilterSet
|
|
2863
|
-
table = tables.SecretsGroupTable
|
|
2770
|
+
return obj
|
|
2864
2771
|
|
|
2865
2772
|
|
|
2866
2773
|
#
|
nautobot/ipam/api/views.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
from django.core.cache import cache
|
|
3
3
|
from django.shortcuts import get_object_or_404
|
|
4
|
-
from drf_spectacular.utils import extend_schema, extend_schema_view
|
|
4
|
+
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
|
5
|
+
import netaddr
|
|
5
6
|
from rest_framework import status
|
|
6
7
|
from rest_framework.decorators import action
|
|
7
8
|
from rest_framework.exceptions import APIException
|
|
@@ -123,6 +124,26 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
123
124
|
return serializers.PrefixLegacySerializer
|
|
124
125
|
return super().get_serializer_class()
|
|
125
126
|
|
|
127
|
+
@staticmethod
|
|
128
|
+
def get_ipaddress_param(request, name, default):
|
|
129
|
+
"""Extract IP address parameter from request.
|
|
130
|
+
:param request: django-rest request object
|
|
131
|
+
:param name: name of the query parameter which contains the IP address string
|
|
132
|
+
:param default: fallback IP address string in case no value is present in the query parameter
|
|
133
|
+
:return: tuple of mutually exclusive (Response|None, netaddr.IPAddress object|None).
|
|
134
|
+
Will return a Response in case the client sent incorrectly formatted IP Address in
|
|
135
|
+
the parameter. It is up to the caller to return the Response.
|
|
136
|
+
"""
|
|
137
|
+
response, result = None, None
|
|
138
|
+
try:
|
|
139
|
+
result = netaddr.IPAddress(request.query_params.get(name, default))
|
|
140
|
+
except (netaddr.core.AddrFormatError, ValueError, TypeError) as e:
|
|
141
|
+
response = Response(
|
|
142
|
+
{"detail": (f"Incorrectly formatted address in parameter {name}: {e}")},
|
|
143
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
144
|
+
)
|
|
145
|
+
return response, result
|
|
146
|
+
|
|
126
147
|
class LocationIncompatibleLegacyBehavior(APIException):
|
|
127
148
|
status_code = 412
|
|
128
149
|
default_detail = (
|
|
@@ -225,6 +246,33 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
225
246
|
|
|
226
247
|
return Response(serializer.data)
|
|
227
248
|
|
|
249
|
+
@extend_schema(
|
|
250
|
+
methods=["get", "post"],
|
|
251
|
+
parameters=[
|
|
252
|
+
OpenApiParameter(
|
|
253
|
+
name="range_start",
|
|
254
|
+
location="query",
|
|
255
|
+
description="IP from which enumeration/allocation should start.",
|
|
256
|
+
type={
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{"type": "string", "format": "ipv6"},
|
|
259
|
+
{"type": "string", "format": "ipv4"},
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
),
|
|
263
|
+
OpenApiParameter(
|
|
264
|
+
name="range_end",
|
|
265
|
+
location="query",
|
|
266
|
+
description="IP from which enumeration/allocation should stop.",
|
|
267
|
+
type={
|
|
268
|
+
"oneOf": [
|
|
269
|
+
{"type": "string", "format": "ipv6"},
|
|
270
|
+
{"type": "string", "format": "ipv4"},
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
),
|
|
274
|
+
],
|
|
275
|
+
)
|
|
228
276
|
@extend_schema(methods=["get"], responses={200: serializers.AvailableIPSerializer(many=True)})
|
|
229
277
|
@extend_schema(
|
|
230
278
|
methods=["post"],
|
|
@@ -251,6 +299,21 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
251
299
|
"""
|
|
252
300
|
prefix = get_object_or_404(Prefix.objects.restrict(request.user), pk=pk)
|
|
253
301
|
|
|
302
|
+
default_first, default_last = netaddr.IPAddress(prefix.prefix.first), netaddr.IPAddress(prefix.prefix.last)
|
|
303
|
+
((error_response_start, range_start), (error_response_end, range_end)) = (
|
|
304
|
+
self.get_ipaddress_param(request, "range_start", default_first),
|
|
305
|
+
self.get_ipaddress_param(request, "range_end", default_last),
|
|
306
|
+
)
|
|
307
|
+
if response := error_response_start or error_response_end:
|
|
308
|
+
return response
|
|
309
|
+
|
|
310
|
+
available_ips = prefix.get_available_ips()
|
|
311
|
+
# range_start and range_end are inclusive
|
|
312
|
+
if range_start > default_first:
|
|
313
|
+
available_ips.remove(netaddr.IPRange(default_first, range_start - 1))
|
|
314
|
+
if range_end < default_last:
|
|
315
|
+
available_ips.remove(netaddr.IPRange(range_end + 1, default_last))
|
|
316
|
+
|
|
254
317
|
# Create the next available IP within the prefix
|
|
255
318
|
if request.method == "POST":
|
|
256
319
|
with cache.lock(
|
|
@@ -260,23 +323,23 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
260
323
|
requested_ips = request.data if isinstance(request.data, list) else [request.data]
|
|
261
324
|
|
|
262
325
|
# Determine if the requested number of IPs is available
|
|
263
|
-
available_ips = prefix.get_available_ips()
|
|
264
326
|
if available_ips.size < len(requested_ips):
|
|
265
327
|
return Response(
|
|
266
328
|
{
|
|
267
329
|
"detail": (
|
|
268
330
|
f"An insufficient number of IP addresses are available within the prefix {prefix} "
|
|
269
|
-
f"({len(requested_ips)} requested, {
|
|
331
|
+
f"({len(requested_ips)} requested, {available_ips.size} available between "
|
|
332
|
+
f"{range_start} and {range_end})."
|
|
270
333
|
)
|
|
271
334
|
},
|
|
272
335
|
status=status.HTTP_204_NO_CONTENT,
|
|
273
336
|
)
|
|
274
337
|
|
|
275
338
|
# Assign addresses from the list of available IPs and copy Namespace assignment from the parent Prefix
|
|
276
|
-
available_ips = iter(available_ips)
|
|
277
339
|
prefix_length = prefix.prefix.prefixlen
|
|
340
|
+
available_ips_iter = iter(available_ips)
|
|
278
341
|
for requested_ip in requested_ips:
|
|
279
|
-
requested_ip["address"] = f"{next(
|
|
342
|
+
requested_ip["address"] = f"{next(available_ips_iter)}/{prefix_length}"
|
|
280
343
|
requested_ip["namespace"] = prefix.namespace
|
|
281
344
|
|
|
282
345
|
# Initialize the serializer with a list or a single object depending on what was requested
|
|
@@ -307,7 +370,7 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
307
370
|
|
|
308
371
|
# Calculate available IPs within the prefix
|
|
309
372
|
ip_list = []
|
|
310
|
-
for index, ip in enumerate(
|
|
373
|
+
for index, ip in enumerate(available_ips, start=1):
|
|
311
374
|
ip_list.append(ip)
|
|
312
375
|
if index == limit:
|
|
313
376
|
break
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 4.2.20 on 2025-07-17 16:10
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("ipam", "0051_added_optional_vrf_relationship_to_vdc"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AlterIndexTogether(
|
|
13
|
+
name="ipaddress",
|
|
14
|
+
index_together={("ip_version", "host", "mask_length")},
|
|
15
|
+
),
|
|
16
|
+
migrations.AlterIndexTogether(
|
|
17
|
+
name="prefix",
|
|
18
|
+
index_together={
|
|
19
|
+
("namespace", "ip_version", "network", "prefix_length"),
|
|
20
|
+
("namespace", "network", "broadcast", "prefix_length"),
|
|
21
|
+
("network", "broadcast", "prefix_length"),
|
|
22
|
+
},
|
|
23
|
+
),
|
|
24
|
+
migrations.AlterIndexTogether(
|
|
25
|
+
name="vrf",
|
|
26
|
+
index_together={("namespace", "name", "rd")},
|
|
27
|
+
),
|
|
28
|
+
]
|
nautobot/ipam/models.py
CHANGED
|
@@ -170,6 +170,9 @@ class VRF(PrimaryModel):
|
|
|
170
170
|
# where multiple different-RD VRFs with the same name may already exist.
|
|
171
171
|
# ["namespace", "name"],
|
|
172
172
|
]
|
|
173
|
+
index_together = [
|
|
174
|
+
["namespace", "name", "rd"],
|
|
175
|
+
]
|
|
173
176
|
verbose_name = "VRF"
|
|
174
177
|
verbose_name_plural = "VRFs"
|
|
175
178
|
|
|
@@ -409,7 +412,12 @@ class VRFPrefixAssignment(BaseModel):
|
|
|
409
412
|
super().clean()
|
|
410
413
|
|
|
411
414
|
if self.prefix.namespace != self.vrf.namespace:
|
|
412
|
-
raise ValidationError(
|
|
415
|
+
raise ValidationError(
|
|
416
|
+
{
|
|
417
|
+
"prefix": f"Prefix (namespace {self.prefix.namespace}) must be in same namespace as "
|
|
418
|
+
"VRF (namespace {self.vrf.namespace})"
|
|
419
|
+
}
|
|
420
|
+
)
|
|
413
421
|
|
|
414
422
|
|
|
415
423
|
@extras_features(
|
|
@@ -589,6 +597,7 @@ class Prefix(PrimaryModel):
|
|
|
589
597
|
index_together = [
|
|
590
598
|
["network", "broadcast", "prefix_length"],
|
|
591
599
|
["namespace", "network", "broadcast", "prefix_length"],
|
|
600
|
+
["namespace", "ip_version", "network", "prefix_length"],
|
|
592
601
|
]
|
|
593
602
|
unique_together = ["namespace", "network", "prefix_length"]
|
|
594
603
|
verbose_name_plural = "prefixes"
|
|
@@ -1250,6 +1259,9 @@ class IPAddress(PrimaryModel):
|
|
|
1250
1259
|
verbose_name = "IP address"
|
|
1251
1260
|
verbose_name_plural = "IP addresses"
|
|
1252
1261
|
unique_together = ["parent", "host"]
|
|
1262
|
+
index_together = [
|
|
1263
|
+
["ip_version", "host", "mask_length"],
|
|
1264
|
+
]
|
|
1253
1265
|
|
|
1254
1266
|
def __init__(self, *args, address=None, namespace=None, **kwargs):
|
|
1255
1267
|
super().__init__(*args, **kwargs)
|