nautobot 2.4.1__py3-none-any.whl → 2.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +135 -0
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/circuits/views.py +4 -1
- nautobot/cloud/api/views.py +3 -3
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/constants.py +0 -1
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +2 -1
- nautobot/core/forms/widgets.py +8 -0
- nautobot/core/management/commands/generate_performance_test_endpoints.py +268 -0
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_create.html +5 -0
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/widgets/clearable_file.html +5 -0
- nautobot/core/templatetags/helpers.py +3 -3
- nautobot/core/testing/integration.py +469 -12
- nautobot/core/tests/test_commands.py +31 -0
- nautobot/core/tests/test_jobs.py +34 -2
- nautobot/core/tests/test_utils.py +17 -2
- nautobot/core/utils/git.py +7 -2
- nautobot/core/utils/lookup.py +12 -1
- nautobot/core/views/generic.py +10 -2
- nautobot/core/views/mixins.py +22 -7
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/api/views.py +11 -10
- nautobot/dcim/forms.py +15 -6
- nautobot/dcim/models/devices.py +1 -2
- nautobot/dcim/tables/devices.py +2 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_trace.html +4 -4
- nautobot/dcim/templates/dcim/consoleport.html +14 -4
- nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/device_component.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/frontport.html +7 -2
- nautobot/dcim/templates/dcim/interface.html +9 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/locationtype.html +1 -1
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -1
- nautobot/dcim/templates/dcim/platform.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +9 -4
- nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +14 -4
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rackgroup.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation.html +2 -2
- nautobot/dcim/templates/dcim/rearport.html +7 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- nautobot/dcim/tests/test_models.py +1 -1
- nautobot/dcim/tests/test_views.py +9 -1
- nautobot/dcim/views.py +12 -15
- nautobot/extras/api/serializers.py +33 -0
- nautobot/extras/api/views.py +13 -5
- nautobot/extras/constants.py +1 -0
- nautobot/extras/datasources/git.py +125 -0
- nautobot/extras/forms/forms.py +4 -0
- nautobot/extras/jobs.py +8 -1
- nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
- nautobot/extras/models/customfields.py +29 -12
- nautobot/extras/models/datasources.py +85 -0
- nautobot/extras/models/models.py +15 -0
- nautobot/extras/models/relationships.py +17 -5
- nautobot/extras/signals.py +15 -1
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/customlink.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/exporttemplate.html +1 -1
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/graphqlquery.html +1 -1
- nautobot/extras/templates/extras/job.html +1 -0
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
- nautobot/extras/templates/extras/jobhook.html +1 -1
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/plugin_detail.html +1 -1
- nautobot/extras/templates/extras/relationship.html +1 -63
- nautobot/extras/templates/extras/role_retrieve.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +1 -1
- nautobot/extras/templates/extras/secret.html +1 -1
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/webhook.html +1 -1
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
- nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
- nautobot/extras/tests/git_helper.py +9 -1
- nautobot/extras/tests/integration/__init__.py +29 -16
- nautobot/extras/tests/test_api.py +6 -0
- nautobot/extras/tests/test_customfields.py +49 -51
- nautobot/extras/tests/test_datasources.py +27 -0
- nautobot/extras/tests/test_dynamicgroups.py +14 -0
- nautobot/extras/tests/test_models.py +283 -0
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/tests/test_views.py +197 -9
- nautobot/extras/utils.py +47 -8
- nautobot/extras/views.py +84 -26
- nautobot/ipam/api/views.py +3 -3
- nautobot/ipam/forms.py +2 -6
- nautobot/ipam/models.py +8 -2
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/templates/ipam/ipaddress.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/rir.html +1 -1
- nautobot/ipam/templates/ipam/routetarget.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +1 -1
- nautobot/ipam/templates/ipam/vrf.html +1 -1
- nautobot/ipam/tests/test_models.py +24 -0
- nautobot/ipam/tests/test_utils.py +41 -2
- nautobot/ipam/utils/__init__.py +18 -11
- nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
- nautobot/project-static/docs/404.html +87 -12
- nautobot/project-static/docs/apps/index.html +88 -13
- nautobot/project-static/docs/apps/nautobot-apps.html +88 -13
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
- nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -21
- nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +89 -14
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
- nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
- nautobot/project-static/docs/development/apps/api/setup.html +88 -13
- nautobot/project-static/docs/development/apps/api/testing.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
- nautobot/project-static/docs/development/apps/index.html +87 -12
- nautobot/project-static/docs/development/apps/migration/code-updates.html +93 -17
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +89 -14
- nautobot/project-static/docs/development/apps/migration/from-v1.html +90 -15
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
- nautobot/project-static/docs/development/core/application-registry.html +87 -12
- nautobot/project-static/docs/development/core/best-practices.html +88 -13
- nautobot/project-static/docs/development/core/bootstrap-ui.html +88 -13
- nautobot/project-static/docs/development/core/caching.html +87 -12
- nautobot/project-static/docs/development/core/controllers.html +87 -12
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +94 -19
- nautobot/project-static/docs/development/core/generic-views.html +87 -12
- nautobot/project-static/docs/development/core/getting-started.html +89 -14
- nautobot/project-static/docs/development/core/homepage.html +87 -12
- nautobot/project-static/docs/development/core/index.html +88 -13
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +90 -15
- nautobot/project-static/docs/development/core/model-checklist.html +88 -13
- nautobot/project-static/docs/development/core/model-features.html +87 -12
- nautobot/project-static/docs/development/core/natural-keys.html +87 -12
- nautobot/project-static/docs/development/core/navigation-menu.html +88 -13
- nautobot/project-static/docs/development/core/release-checklist.html +88 -13
- nautobot/project-static/docs/development/core/role-internals.html +87 -12
- nautobot/project-static/docs/development/core/settings.html +88 -13
- nautobot/project-static/docs/development/core/style-guide.html +91 -16
- nautobot/project-static/docs/development/core/templates.html +88 -13
- nautobot/project-static/docs/development/core/testing.html +87 -12
- nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
- nautobot/project-static/docs/development/core/user-preferences.html +87 -12
- nautobot/project-static/docs/development/index.html +87 -12
- nautobot/project-static/docs/development/jobs/index.html +95 -13
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +90 -14
- nautobot/project-static/docs/index.html +90 -14
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +89 -14
- nautobot/project-static/docs/overview/design_philosophy.html +87 -12
- nautobot/project-static/docs/release-notes/index.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.1.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.2.html +90 -15
- nautobot/project-static/docs/release-notes/version-1.3.html +88 -13
- nautobot/project-static/docs/release-notes/version-1.4.html +104 -29
- nautobot/project-static/docs/release-notes/version-1.5.html +95 -20
- nautobot/project-static/docs/release-notes/version-1.6.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.0.html +97 -22
- nautobot/project-static/docs/release-notes/version-2.1.html +94 -19
- nautobot/project-static/docs/release-notes/version-2.2.html +88 -13
- nautobot/project-static/docs/release-notes/version-2.3.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.4.html +465 -12
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +296 -288
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +91 -16
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +88 -13
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +95 -20
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +90 -15
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +88 -13
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +91 -16
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +102 -27
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +89 -14
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +93 -18
- nautobot/project-static/docs/user-guide/administration/installation/services.html +88 -13
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
- nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9844 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +88 -13
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +98 -20
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +99 -24
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +188 -30
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
- nautobot/project-static/docs/user-guide/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +88 -13
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +90 -15
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +89 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +93 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
- nautobot/project-static/js/dropdown.js +28 -0
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
- nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
- nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
- nautobot/tenancy/tests/test_views.py +5 -1
- nautobot/tenancy/urls.py +7 -79
- nautobot/tenancy/views.py +51 -80
- nautobot/virtualization/templates/virtualization/cluster.html +1 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
- nautobot/wireless/api/serializers.py +6 -1
- nautobot/wireless/api/views.py +3 -3
- nautobot/wireless/tests/test_api.py +5 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/METADATA +12 -12
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/RECORD +459 -443
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
- /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/WHEEL +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -30,6 +30,7 @@ from nautobot.extras.models import (
|
|
|
30
30
|
DynamicGroup,
|
|
31
31
|
ExportTemplate,
|
|
32
32
|
GitRepository,
|
|
33
|
+
GraphQLQuery,
|
|
33
34
|
Job,
|
|
34
35
|
JobQueue,
|
|
35
36
|
JobResult,
|
|
@@ -935,6 +936,120 @@ def delete_git_export_templates(repository_record, job_result, preserve=None):
|
|
|
935
936
|
job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="export templates")
|
|
936
937
|
|
|
937
938
|
|
|
939
|
+
#
|
|
940
|
+
# GraphQL handling
|
|
941
|
+
#
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def refresh_git_graphql_queries(repository_record, job_result, delete=False):
|
|
945
|
+
"""Callback function for GitRepository updates - refresh all GraphQLQuery managed by this repository."""
|
|
946
|
+
if "extras.graphqlquery" in repository_record.provided_contents and not delete:
|
|
947
|
+
update_git_graphql_queries(repository_record, job_result)
|
|
948
|
+
else:
|
|
949
|
+
delete_git_graphql_queries(repository_record, job_result)
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
logger = logging.getLogger(__name__)
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
def update_git_graphql_queries(repository_record, job_result):
|
|
956
|
+
"""Refresh any GraphQL queries provided by this Git repository."""
|
|
957
|
+
graphql_query_path = os.path.join(repository_record.filesystem_path, "graphql_queries")
|
|
958
|
+
git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
|
|
959
|
+
graphql_queries = []
|
|
960
|
+
|
|
961
|
+
if os.path.isdir(graphql_query_path):
|
|
962
|
+
for file in os.listdir(graphql_query_path):
|
|
963
|
+
file_path = os.path.join(graphql_query_path, file)
|
|
964
|
+
if not os.path.isfile(file_path):
|
|
965
|
+
continue
|
|
966
|
+
|
|
967
|
+
# Remove `.gql` extension from the name if it exists
|
|
968
|
+
query_name = file.rsplit(".gql", 1)[0] if file.endswith(".gql") else file
|
|
969
|
+
|
|
970
|
+
try:
|
|
971
|
+
with open(file_path, "r") as fd:
|
|
972
|
+
query_content = fd.read().strip()
|
|
973
|
+
|
|
974
|
+
graphql_query, created = GraphQLQuery.objects.get_or_create(
|
|
975
|
+
name=query_name,
|
|
976
|
+
owner_content_type=git_repository_content_type,
|
|
977
|
+
owner_object_id=repository_record.pk,
|
|
978
|
+
defaults={"query": query_content},
|
|
979
|
+
)
|
|
980
|
+
modified = graphql_query.query != query_content
|
|
981
|
+
graphql_queries.append(query_name)
|
|
982
|
+
# Only attempt to update if the content has changed
|
|
983
|
+
if modified:
|
|
984
|
+
try:
|
|
985
|
+
graphql_query.query = query_content
|
|
986
|
+
graphql_query.validated_save()
|
|
987
|
+
msg = (
|
|
988
|
+
f"Successfully created GraphQL query: {query_name}"
|
|
989
|
+
if created
|
|
990
|
+
else f"Successfully updated GraphQL query: {query_name}"
|
|
991
|
+
)
|
|
992
|
+
logger.info(msg)
|
|
993
|
+
job_result.log(
|
|
994
|
+
msg, obj=graphql_query, level_choice=LogLevelChoices.LOG_INFO, grouping="graphql queries"
|
|
995
|
+
)
|
|
996
|
+
except Exception as exc:
|
|
997
|
+
# Log validation error and retain the existing query
|
|
998
|
+
error_msg = (
|
|
999
|
+
f"Invalid GraphQL syntax for query '{query_name}'. "
|
|
1000
|
+
f"Retaining the existing query. Error: {exc}"
|
|
1001
|
+
)
|
|
1002
|
+
logger.error(error_msg)
|
|
1003
|
+
job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
|
|
1004
|
+
continue
|
|
1005
|
+
else:
|
|
1006
|
+
msg = f"No changes to GraphQL query: {query_name}"
|
|
1007
|
+
logger.info(msg)
|
|
1008
|
+
job_result.log(
|
|
1009
|
+
msg, obj=graphql_query, level_choice=LogLevelChoices.LOG_INFO, grouping="graphql queries"
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
except Exception as exc:
|
|
1013
|
+
# Check if a query with the same name already exists
|
|
1014
|
+
existing_query = GraphQLQuery.objects.filter(name=query_name).first()
|
|
1015
|
+
if existing_query and existing_query.owner_object_id != repository_record.pk:
|
|
1016
|
+
error_msg = (
|
|
1017
|
+
f"GraphQL query '{query_name}' already exists "
|
|
1018
|
+
f"Please rename the query in the repository and try again."
|
|
1019
|
+
)
|
|
1020
|
+
else:
|
|
1021
|
+
error_msg = f"Error processing GraphQL query file '{file}': {exc}"
|
|
1022
|
+
|
|
1023
|
+
# Log the error
|
|
1024
|
+
logger.error(error_msg)
|
|
1025
|
+
job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
|
|
1026
|
+
|
|
1027
|
+
# Delete any queries not in the preserved list
|
|
1028
|
+
delete_git_graphql_queries(repository_record, job_result, preserve=graphql_queries)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def delete_git_graphql_queries(repository_record, job_result, preserve=None):
|
|
1032
|
+
"""Delete GraphQL queries owned by the given Git repository that are not in the preserve list."""
|
|
1033
|
+
git_repository_content_type = ContentType.objects.get_for_model(GitRepository)
|
|
1034
|
+
if preserve is None:
|
|
1035
|
+
preserve = []
|
|
1036
|
+
|
|
1037
|
+
for graphql_query in GraphQLQuery.objects.filter(
|
|
1038
|
+
owner_content_type=git_repository_content_type,
|
|
1039
|
+
owner_object_id=repository_record.pk,
|
|
1040
|
+
):
|
|
1041
|
+
if graphql_query.name not in preserve:
|
|
1042
|
+
try:
|
|
1043
|
+
graphql_query.delete()
|
|
1044
|
+
msg = f"Deleted GraphQL query: {graphql_query.name}"
|
|
1045
|
+
logger.warning(msg)
|
|
1046
|
+
job_result.log(msg, level_choice=LogLevelChoices.LOG_WARNING, grouping="graphql queries")
|
|
1047
|
+
except Exception as exc:
|
|
1048
|
+
error_msg = f"Unable to delete '{graphql_query.name}': {exc}"
|
|
1049
|
+
logger.error(error_msg)
|
|
1050
|
+
job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
|
|
1051
|
+
|
|
1052
|
+
|
|
938
1053
|
# Register built-in callbacks for data types potentially provided by a GitRepository
|
|
939
1054
|
register_datasource_contents(
|
|
940
1055
|
[
|
|
@@ -978,5 +1093,15 @@ register_datasource_contents(
|
|
|
978
1093
|
callback=refresh_git_export_templates,
|
|
979
1094
|
),
|
|
980
1095
|
),
|
|
1096
|
+
(
|
|
1097
|
+
"extras.gitrepository",
|
|
1098
|
+
DatasourceContent(
|
|
1099
|
+
name="graphql queries",
|
|
1100
|
+
content_identifier="extras.graphqlquery",
|
|
1101
|
+
icon="mdi-graphql",
|
|
1102
|
+
weight=400,
|
|
1103
|
+
callback=refresh_git_graphql_queries,
|
|
1104
|
+
),
|
|
1105
|
+
),
|
|
981
1106
|
]
|
|
982
1107
|
)
|
nautobot/extras/forms/forms.py
CHANGED
|
@@ -38,6 +38,7 @@ from nautobot.core.forms import (
|
|
|
38
38
|
)
|
|
39
39
|
from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
|
|
40
40
|
from nautobot.core.forms.forms import ConfirmationForm
|
|
41
|
+
from nautobot.core.forms.widgets import ClearableFileInput
|
|
41
42
|
from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
|
|
42
43
|
from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
|
|
43
44
|
from nautobot.extras.choices import (
|
|
@@ -985,6 +986,9 @@ class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
|
|
|
985
986
|
"name",
|
|
986
987
|
"image",
|
|
987
988
|
]
|
|
989
|
+
widgets = {
|
|
990
|
+
"image": ClearableFileInput,
|
|
991
|
+
}
|
|
988
992
|
|
|
989
993
|
|
|
990
994
|
#
|
nautobot/extras/jobs.py
CHANGED
|
@@ -37,6 +37,7 @@ from nautobot.core.forms import (
|
|
|
37
37
|
DynamicModelMultipleChoiceField,
|
|
38
38
|
JSONField,
|
|
39
39
|
)
|
|
40
|
+
from nautobot.core.forms.widgets import ClearableFileInput
|
|
40
41
|
from nautobot.core.utils.config import get_settings_or_config
|
|
41
42
|
from nautobot.core.utils.logging import sanitize
|
|
42
43
|
from nautobot.core.utils.lookup import get_model_from_name
|
|
@@ -1040,12 +1041,18 @@ class DatabaseFileField(forms.FileField):
|
|
|
1040
1041
|
widget = DBClearableFileInput
|
|
1041
1042
|
|
|
1042
1043
|
|
|
1044
|
+
class BootstrapStyleFileField(forms.FileField):
|
|
1045
|
+
"""File picker with UX bootstrap style and clearable checkbox."""
|
|
1046
|
+
|
|
1047
|
+
widget = ClearableFileInput
|
|
1048
|
+
|
|
1049
|
+
|
|
1043
1050
|
class FileVar(ScriptVariable):
|
|
1044
1051
|
"""
|
|
1045
1052
|
An uploaded file.
|
|
1046
1053
|
"""
|
|
1047
1054
|
|
|
1048
|
-
form_field =
|
|
1055
|
+
form_field = BootstrapStyleFileField
|
|
1049
1056
|
|
|
1050
1057
|
|
|
1051
1058
|
class IPAddressVar(ScriptVariable):
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by Django 4.2.17 on 2025-01-08 16:59
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
import nautobot.extras.utils
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
dependencies = [
|
|
11
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
12
|
+
("extras", "0121_alter_team_contacts"),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name="graphqlquery",
|
|
18
|
+
name="owner_content_type",
|
|
19
|
+
field=models.ForeignKey(
|
|
20
|
+
blank=True,
|
|
21
|
+
default=None,
|
|
22
|
+
limit_choices_to=nautobot.extras.utils.FeatureQuery("graphql_query_owners"),
|
|
23
|
+
null=True,
|
|
24
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
25
|
+
related_name="graphql_queries",
|
|
26
|
+
to="contenttypes.contenttype",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
migrations.AddField(
|
|
30
|
+
model_name="graphqlquery",
|
|
31
|
+
name="owner_object_id",
|
|
32
|
+
field=models.UUIDField(blank=True, default=None, null=True),
|
|
33
|
+
),
|
|
34
|
+
]
|
|
@@ -370,11 +370,11 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
|
370
370
|
|
|
371
371
|
def get_for_model(self, model, exclude_filter_disabled=False):
|
|
372
372
|
"""
|
|
373
|
-
Return all CustomFields assigned to the given model.
|
|
373
|
+
Return (and cache) all CustomFields assigned to the given model.
|
|
374
374
|
|
|
375
375
|
Args:
|
|
376
|
-
model: The django model to which custom fields are registered
|
|
377
|
-
exclude_filter_disabled: Exclude any custom fields which have filter logic disabled
|
|
376
|
+
model (Model): The django model to which custom fields are registered
|
|
377
|
+
exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
|
|
378
378
|
"""
|
|
379
379
|
concrete_model = model._meta.concrete_model
|
|
380
380
|
cache_key = (
|
|
@@ -521,6 +521,26 @@ class CustomField(
|
|
|
521
521
|
def __str__(self):
|
|
522
522
|
return self.label
|
|
523
523
|
|
|
524
|
+
@property
|
|
525
|
+
def choices_cache_key(self):
|
|
526
|
+
return f"nautobot.extras.customfield.choices.{self.pk}"
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def choices(self) -> list[str]:
|
|
530
|
+
"""
|
|
531
|
+
Cacheable shorthand for retrieving custom_field_choices values associated with this model.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
list[str]: List of choice values, ordered by weight.
|
|
535
|
+
"""
|
|
536
|
+
if self.type not in [CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT]:
|
|
537
|
+
return []
|
|
538
|
+
choices = cache.get(self.choices_cache_key)
|
|
539
|
+
if choices is None:
|
|
540
|
+
choices = list(self.custom_field_choices.order_by("weight", "value").values_list("value", flat=True))
|
|
541
|
+
cache.set(self.choices_cache_key, choices)
|
|
542
|
+
return choices
|
|
543
|
+
|
|
524
544
|
def save(self, *args, **kwargs):
|
|
525
545
|
self.clean()
|
|
526
546
|
super().save(*args, **kwargs)
|
|
@@ -679,12 +699,11 @@ class CustomField(
|
|
|
679
699
|
|
|
680
700
|
# Select or Multi-select
|
|
681
701
|
else:
|
|
682
|
-
choices = [(
|
|
683
|
-
default_choice = self.custom_field_choices.filter(value=self.default).first()
|
|
702
|
+
choices = [(value, value) for value in self.choices]
|
|
684
703
|
|
|
685
704
|
# Set the initial value to the first available choice (if any)
|
|
686
705
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filter_form:
|
|
687
|
-
if not required or
|
|
706
|
+
if not required or self.default not in self.choices:
|
|
688
707
|
choices = add_blank_choice(choices)
|
|
689
708
|
field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
|
|
690
709
|
field = field_class(
|
|
@@ -779,17 +798,15 @@ class CustomField(
|
|
|
779
798
|
|
|
780
799
|
# Validate selected choice
|
|
781
800
|
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
|
782
|
-
if value not in self.
|
|
783
|
-
raise ValidationError(
|
|
784
|
-
f"Invalid choice ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
|
|
785
|
-
)
|
|
801
|
+
if value not in self.choices:
|
|
802
|
+
raise ValidationError(f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}")
|
|
786
803
|
|
|
787
804
|
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
|
788
805
|
if isinstance(value, str):
|
|
789
806
|
value = value.split(",")
|
|
790
|
-
if not set(value).issubset(self.
|
|
807
|
+
if not set(value).issubset(self.choices):
|
|
791
808
|
raise ValidationError(
|
|
792
|
-
f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.
|
|
809
|
+
f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.choices)}"
|
|
793
810
|
)
|
|
794
811
|
|
|
795
812
|
elif self.required:
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Models for representing external data sources."""
|
|
2
2
|
|
|
3
|
+
from contextlib import contextmanager
|
|
3
4
|
from importlib.util import find_spec
|
|
5
|
+
import logging
|
|
4
6
|
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
5
9
|
|
|
6
10
|
from django.conf import settings
|
|
7
11
|
from django.core.exceptions import ValidationError
|
|
@@ -12,12 +16,16 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
|
12
16
|
from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
|
|
13
17
|
from nautobot.core.models.generics import PrimaryModel
|
|
14
18
|
from nautobot.core.models.validators import EnhancedURLValidator
|
|
19
|
+
from nautobot.core.utils.git import GitRepo
|
|
15
20
|
from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
|
|
16
21
|
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
17
24
|
|
|
18
25
|
@extras_features(
|
|
19
26
|
"config_context_owners",
|
|
20
27
|
"export_template_owners",
|
|
28
|
+
"graphql_query_owners",
|
|
21
29
|
"graphql",
|
|
22
30
|
"job_results",
|
|
23
31
|
"webhooks",
|
|
@@ -167,3 +175,80 @@ class GitRepository(PrimaryModel):
|
|
|
167
175
|
if dry_run:
|
|
168
176
|
return enqueue_git_repository_diff_origin_and_local(self, user)
|
|
169
177
|
return enqueue_pull_git_repository_and_refresh_data(self, user)
|
|
178
|
+
|
|
179
|
+
@contextmanager
|
|
180
|
+
def clone_to_directory_context(self, path=None, branch=None, head=None, depth=0):
|
|
181
|
+
"""
|
|
182
|
+
Context manager to perform a (shallow or full) clone of the Git repository in a temporary directory.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
|
|
186
|
+
branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
|
|
187
|
+
head (str, optional): Git commit hash to check out instead of pulling branch latest.
|
|
188
|
+
depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
if branch and head:
|
|
195
|
+
raise ValueError("Cannot specify both branch and head")
|
|
196
|
+
|
|
197
|
+
path_name = None
|
|
198
|
+
try:
|
|
199
|
+
path_name = self.clone_to_directory(path=path, branch=branch, head=head, depth=depth)
|
|
200
|
+
yield path_name
|
|
201
|
+
finally:
|
|
202
|
+
# Cleanup the temporary directory
|
|
203
|
+
if path_name:
|
|
204
|
+
self.cleanup_cloned_directory(path_name)
|
|
205
|
+
|
|
206
|
+
def clone_to_directory(self, path=None, branch=None, head=None, depth=0):
|
|
207
|
+
"""
|
|
208
|
+
Perform a (shallow or full) clone of the Git repository in a temporary directory.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
|
|
212
|
+
branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
|
|
213
|
+
head (str, optional): Git commit hash to check out instead of pulling branch latest.
|
|
214
|
+
depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
|
|
218
|
+
"""
|
|
219
|
+
if branch and head:
|
|
220
|
+
raise ValueError("Cannot specify both branch and head")
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
path_name = tempfile.mkdtemp(dir=path, prefix=self.slug)
|
|
224
|
+
except PermissionError as e:
|
|
225
|
+
logger.error(f"Failed to create temporary directory at {path}: {e}")
|
|
226
|
+
raise e
|
|
227
|
+
|
|
228
|
+
if not branch:
|
|
229
|
+
branch = self.branch
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
repo_helper = GitRepo(path_name, self.remote_url, depth=depth, branch=branch)
|
|
233
|
+
if head:
|
|
234
|
+
repo_helper.checkout(branch, head)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Failed to clone repository {self.name} to {path_name}: {e}")
|
|
237
|
+
raise e
|
|
238
|
+
|
|
239
|
+
logger.info(f"Cloned repository {self.name} to {path_name}")
|
|
240
|
+
return path_name
|
|
241
|
+
|
|
242
|
+
def cleanup_cloned_directory(self, path):
|
|
243
|
+
"""
|
|
244
|
+
Cleanup the cloned directory.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
path (str): The absolute directory path to cleanup.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
shutil.rmtree(path)
|
|
252
|
+
except OSError as os_error:
|
|
253
|
+
# log error if the cleanup fails
|
|
254
|
+
logger.error(f"Failed to cleanup temporary directory at {path}: {os_error}")
|
nautobot/extras/models/models.py
CHANGED
|
@@ -680,6 +680,21 @@ class GraphQLQuery(
|
|
|
680
680
|
SavedViewMixin,
|
|
681
681
|
BaseModel,
|
|
682
682
|
):
|
|
683
|
+
# A Graphql Query *may* be owned by another model, such as a GitRepository, or it may be un-owned
|
|
684
|
+
owner_content_type = models.ForeignKey(
|
|
685
|
+
to=ContentType,
|
|
686
|
+
on_delete=models.CASCADE,
|
|
687
|
+
limit_choices_to=FeatureQuery("graphql_query_owners"),
|
|
688
|
+
default=None,
|
|
689
|
+
null=True,
|
|
690
|
+
blank=True,
|
|
691
|
+
related_name="graphql_queries",
|
|
692
|
+
)
|
|
693
|
+
owner_object_id = models.UUIDField(default=None, null=True, blank=True)
|
|
694
|
+
owner = GenericForeignKey(
|
|
695
|
+
ct_field="owner_content_type",
|
|
696
|
+
fk_field="owner_object_id",
|
|
697
|
+
)
|
|
683
698
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
|
|
684
699
|
query = models.TextField()
|
|
685
700
|
variables = models.JSONField(encoder=DjangoJSONEncoder, default=dict, blank=True)
|
|
@@ -261,15 +261,27 @@ class RelationshipModel(models.Model):
|
|
|
261
261
|
remote_model = remote_ct.model_class()
|
|
262
262
|
if remote_model is not None:
|
|
263
263
|
if not relationship.symmetric:
|
|
264
|
-
query_params = {
|
|
265
|
-
|
|
264
|
+
query_params = {
|
|
265
|
+
f"{peer_side}_for_associations__relationship": relationship,
|
|
266
|
+
f"{peer_side}_for_associations__{side}_id": self.pk,
|
|
267
|
+
}
|
|
268
|
+
# Get the related objects for this relationship on the opposite side.
|
|
269
|
+
resp[side][relationship] = remote_model.objects.filter(**query_params).distinct()
|
|
266
270
|
if not relationship.has_many(peer_side):
|
|
267
271
|
resp[side][relationship] = resp[side][relationship].first()
|
|
268
272
|
else:
|
|
273
|
+
side_query_params = {
|
|
274
|
+
f"{peer_side}_for_associations__relationship": relationship,
|
|
275
|
+
f"{peer_side}_for_associations__{side}_id": self.pk,
|
|
276
|
+
}
|
|
277
|
+
peer_side_query_params = {
|
|
278
|
+
f"{side}_for_associations__relationship": relationship,
|
|
279
|
+
f"{side}_for_associations__{peer_side}_id": self.pk,
|
|
280
|
+
}
|
|
281
|
+
# Get the related objects based on the pks we gathered.
|
|
269
282
|
resp[RelationshipSideChoices.SIDE_PEER][relationship] = remote_model.objects.filter(
|
|
270
|
-
Q(
|
|
271
|
-
|
|
272
|
-
).exclude(pk=self.pk)
|
|
283
|
+
Q(**side_query_params) | Q(**peer_side_query_params)
|
|
284
|
+
).distinct()
|
|
273
285
|
if not relationship.has_many(peer_side):
|
|
274
286
|
resp[side][relationship] = resp[side][relationship].first()
|
|
275
287
|
else:
|
nautobot/extras/signals.py
CHANGED
|
@@ -28,6 +28,7 @@ from nautobot.extras.models import (
|
|
|
28
28
|
ComputedField,
|
|
29
29
|
ContactAssociation,
|
|
30
30
|
CustomField,
|
|
31
|
+
CustomFieldChoice,
|
|
31
32
|
DynamicGroup,
|
|
32
33
|
DynamicGroupMembership,
|
|
33
34
|
GitRepository,
|
|
@@ -97,6 +98,19 @@ def invalidate_models_cache(sender, **kwargs):
|
|
|
97
98
|
cache.delete_pattern(f"{manager.keys_for_model.cache_key_prefix}.*")
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
@receiver(post_delete, sender=CustomField)
|
|
102
|
+
@receiver(post_delete, sender=CustomFieldChoice)
|
|
103
|
+
@receiver(post_save, sender=CustomFieldChoice)
|
|
104
|
+
@receiver(post_save, sender=CustomField)
|
|
105
|
+
def invalidate_choices_cache(sender, instance, **kwargs):
|
|
106
|
+
"""Invalidate the choices cache for CustomFields."""
|
|
107
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
108
|
+
if sender is CustomField:
|
|
109
|
+
cache.delete(instance.choices_cache_key)
|
|
110
|
+
else:
|
|
111
|
+
cache.delete(instance.custom_field.choices_cache_key)
|
|
112
|
+
|
|
113
|
+
|
|
100
114
|
@receiver(post_save, sender=Relationship)
|
|
101
115
|
@receiver(m2m_changed, sender=Relationship)
|
|
102
116
|
@receiver(post_delete, sender=Relationship)
|
|
@@ -376,7 +390,7 @@ def git_repository_pre_delete(instance, **kwargs):
|
|
|
376
390
|
app.control.broadcast("discard_git_repository", repository_slug=instance.slug)
|
|
377
391
|
# But we don't have an equivalent way to broadcast to any other Django instances.
|
|
378
392
|
# For now we just delete the one that we have locally and rely on other methods,
|
|
379
|
-
# such as the import_jobs() signal that runs on
|
|
393
|
+
# such as the import_jobs() signal that runs on post migrate,
|
|
380
394
|
# to clean up other clones as they're encountered.
|
|
381
395
|
if os.path.isdir(instance.filesystem_path):
|
|
382
396
|
shutil.rmtree(instance.filesystem_path)
|