nautobot 2.3.8__py3-none-any.whl → 2.3.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/tables.py +2 -0
- nautobot/core/forms/__init__.py +4 -0
- nautobot/core/forms/fields.py +32 -0
- nautobot/core/jobs/__init__.py +24 -8
- nautobot/core/models/query_functions.py +147 -1
- nautobot/core/models/tree_queries.py +8 -0
- nautobot/core/settings.py +7 -0
- nautobot/core/settings.yaml +10 -0
- nautobot/core/signals.py +5 -4
- nautobot/core/templates/nautobot_config.py.j2 +4 -0
- nautobot/core/tests/test_models_query_functions.py +108 -0
- nautobot/dcim/forms.py +30 -27
- nautobot/dcim/models/device_components.py +5 -0
- nautobot/dcim/tables/devices.py +4 -2
- nautobot/dcim/templates/dcim/modulebay_create.html +39 -0
- nautobot/dcim/templates/dcim/modulebay_update.html +39 -0
- nautobot/dcim/tests/test_models.py +16 -0
- nautobot/dcim/views.py +1 -1
- nautobot/extras/api/customfields.py +3 -10
- nautobot/extras/context_managers.py +28 -9
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +30 -49
- nautobot/extras/datasources/registry.py +2 -2
- nautobot/extras/jobs.py +30 -12
- nautobot/extras/models/customfields.py +12 -0
- nautobot/extras/models/datasources.py +6 -0
- nautobot/extras/models/groups.py +47 -33
- nautobot/extras/models/jobs.py +1 -1
- nautobot/extras/plugins/__init__.py +165 -0
- nautobot/extras/signals.py +2 -0
- nautobot/extras/tasks.py +88 -69
- nautobot/extras/templates/extras/plugin_detail.html +33 -0
- nautobot/extras/tests/test_context_managers.py +23 -9
- nautobot/extras/tests/test_datasources.py +88 -1
- nautobot/extras/tests/test_dynamicgroups.py +12 -0
- nautobot/extras/tests/test_plugins.py +94 -0
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/views.py +3 -1
- nautobot/extras/webhooks.py +16 -7
- nautobot/project-static/docs/404.html +24 -3
- nautobot/project-static/docs/apps/index.html +24 -3
- nautobot/project-static/docs/apps/nautobot-apps.html +24 -3
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js → bundle.83f73b43.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js.map → bundle.83f73b43.min.js.map} +3 -3
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +106 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +138 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +24 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +24 -3
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/global-search.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +27 -6
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +8823 -0
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +27 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +24 -3
- nautobot/project-static/docs/development/apps/api/setup.html +33 -11
- nautobot/project-static/docs/development/apps/api/testing.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/notes.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/urls.html +24 -3
- nautobot/project-static/docs/development/apps/index.html +24 -3
- nautobot/project-static/docs/development/apps/migration/code-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +24 -3
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +24 -3
- nautobot/project-static/docs/development/core/application-registry.html +24 -3
- nautobot/project-static/docs/development/core/best-practices.html +24 -3
- nautobot/project-static/docs/development/core/bootstrap-ui.html +24 -3
- nautobot/project-static/docs/development/core/caching.html +24 -3
- nautobot/project-static/docs/development/core/controllers.html +24 -3
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +24 -3
- nautobot/project-static/docs/development/core/generic-views.html +24 -3
- nautobot/project-static/docs/development/core/getting-started.html +24 -3
- nautobot/project-static/docs/development/core/homepage.html +24 -3
- nautobot/project-static/docs/development/core/index.html +24 -3
- nautobot/project-static/docs/development/core/model-checklist.html +24 -3
- nautobot/project-static/docs/development/core/model-features.html +24 -3
- nautobot/project-static/docs/development/core/natural-keys.html +24 -3
- nautobot/project-static/docs/development/core/navigation-menu.html +24 -3
- nautobot/project-static/docs/development/core/release-checklist.html +24 -3
- nautobot/project-static/docs/development/core/role-internals.html +24 -3
- nautobot/project-static/docs/development/core/settings.html +24 -3
- nautobot/project-static/docs/development/core/style-guide.html +24 -3
- nautobot/project-static/docs/development/core/templates.html +24 -3
- nautobot/project-static/docs/development/core/testing.html +24 -3
- nautobot/project-static/docs/development/core/user-preferences.html +24 -3
- nautobot/project-static/docs/development/index.html +24 -3
- nautobot/project-static/docs/development/jobs/index.html +24 -3
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +24 -3
- nautobot/project-static/docs/index.html +24 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +24 -3
- nautobot/project-static/docs/overview/design_philosophy.html +24 -3
- nautobot/project-static/docs/release-notes/index.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.3.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.4.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.6.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.3.html +430 -114
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +273 -269
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -4
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/services.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +24 -3
- nautobot/project-static/docs/user-guide/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +24 -3
- nautobot/project-static/js/forms.js +3 -5
- nautobot/virtualization/tables.py +1 -1
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/METADATA +3 -3
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/RECORD +327 -323
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +0 -1
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/NOTICE +0 -0
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/WHEEL +0 -0
- {nautobot-2.3.8.dist-info → nautobot-2.3.10.dist-info}/entry_points.txt +0 -0
nautobot/apps/tables.py
CHANGED
|
@@ -15,6 +15,7 @@ from nautobot.core.tables import (
|
|
|
15
15
|
TagColumn,
|
|
16
16
|
ToggleColumn,
|
|
17
17
|
)
|
|
18
|
+
from nautobot.extras.plugins import TableExtension
|
|
18
19
|
from nautobot.extras.tables import RoleTableMixin, StatusTableMixin
|
|
19
20
|
|
|
20
21
|
__all__ = (
|
|
@@ -32,5 +33,6 @@ __all__ = (
|
|
|
32
33
|
"RoleTableMixin",
|
|
33
34
|
"StatusTableMixin",
|
|
34
35
|
"TagColumn",
|
|
36
|
+
"TableExtension",
|
|
35
37
|
"ToggleColumn",
|
|
36
38
|
)
|
nautobot/core/forms/__init__.py
CHANGED
|
@@ -7,6 +7,8 @@ from nautobot.core.forms.constants import (
|
|
|
7
7
|
NUMERIC_EXPANSION_PATTERN,
|
|
8
8
|
)
|
|
9
9
|
from nautobot.core.forms.fields import (
|
|
10
|
+
AutoPositionField,
|
|
11
|
+
AutoPositionPatternField,
|
|
10
12
|
CommentField,
|
|
11
13
|
CSVChoiceField,
|
|
12
14
|
CSVContentTypeField,
|
|
@@ -80,6 +82,8 @@ __all__ = (
|
|
|
80
82
|
"ALPHANUMERIC_EXPANSION_PATTERN",
|
|
81
83
|
"APISelect",
|
|
82
84
|
"APISelectMultiple",
|
|
85
|
+
"AutoPositionField",
|
|
86
|
+
"AutoPositionPatternField",
|
|
83
87
|
"BOOLEAN_CHOICES",
|
|
84
88
|
"BOOLEAN_WITH_BLANK_CHOICES",
|
|
85
89
|
"BootstrapMixin",
|
nautobot/core/forms/fields.py
CHANGED
|
@@ -444,6 +444,38 @@ class SlugField(django_forms.SlugField):
|
|
|
444
444
|
self.widget.attrs["slug-source"] = slug_source
|
|
445
445
|
|
|
446
446
|
|
|
447
|
+
class AutoPositionField(django_forms.CharField):
|
|
448
|
+
def __init__(self, source="name", *args, **kwargs):
|
|
449
|
+
"""
|
|
450
|
+
Instantiate a AutoPositionField.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
source (str, tuple): Name of the field (or a list of field names) that will be used to suggest a position.
|
|
454
|
+
"""
|
|
455
|
+
kwargs.setdefault("label", "Position")
|
|
456
|
+
kwargs.setdefault("widget", forms.SlugWidget)
|
|
457
|
+
super().__init__(*args, **kwargs)
|
|
458
|
+
if isinstance(source, (tuple, list)):
|
|
459
|
+
source = " ".join(source)
|
|
460
|
+
self.widget.attrs["source"] = source
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class AutoPositionPatternField(ExpandableNameField):
|
|
464
|
+
def __init__(self, source="name_pattern", *args, **kwargs):
|
|
465
|
+
"""
|
|
466
|
+
Instantiate a AutoPositionPatternField.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
source (str, tuple): Name pattern of the field (or a list of field names) that will be used to suggest a position pattern.
|
|
470
|
+
"""
|
|
471
|
+
kwargs.setdefault("label", "Position")
|
|
472
|
+
kwargs.setdefault("widget", forms.SlugWidget)
|
|
473
|
+
super().__init__(*args, **kwargs)
|
|
474
|
+
if isinstance(source, (tuple, list)):
|
|
475
|
+
source = " ".join(source)
|
|
476
|
+
self.widget.attrs["source"] = source
|
|
477
|
+
|
|
478
|
+
|
|
447
479
|
class DynamicModelChoiceMixin:
|
|
448
480
|
"""
|
|
449
481
|
:param display_field: The name of the attribute of an API response object to display in the selection list
|
nautobot/core/jobs/__init__.py
CHANGED
|
@@ -19,7 +19,12 @@ from nautobot.core.jobs.cleanup import LogsCleanup
|
|
|
19
19
|
from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
|
|
20
20
|
from nautobot.core.utils.lookup import get_filterset_for_model
|
|
21
21
|
from nautobot.core.utils.requests import get_filterable_params_from_filter_params
|
|
22
|
-
from nautobot.extras.datasources import
|
|
22
|
+
from nautobot.extras.datasources import (
|
|
23
|
+
ensure_git_repository,
|
|
24
|
+
git_repository_dry_run,
|
|
25
|
+
refresh_datasource_content,
|
|
26
|
+
refresh_job_code_from_repository,
|
|
27
|
+
)
|
|
23
28
|
from nautobot.extras.jobs import BooleanVar, ChoiceVar, FileVar, Job, ObjectVar, RunJobTaskFailed, StringVar, TextVar
|
|
24
29
|
from nautobot.extras.models import ExportTemplate, GitRepository
|
|
25
30
|
|
|
@@ -49,13 +54,24 @@ class GitRepositorySync(Job):
|
|
|
49
54
|
self.logger.info(f'Creating/refreshing local copy of Git repository "{repository.name}"...')
|
|
50
55
|
|
|
51
56
|
try:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
with transaction.atomic():
|
|
58
|
+
ensure_git_repository(repository, logger=self.logger)
|
|
59
|
+
refresh_datasource_content("extras.gitrepository", repository, user, job_result, delete=False)
|
|
60
|
+
# Given that the above succeeded, tell all workers (including ourself) to call ensure_git_repository()
|
|
61
|
+
app.control.broadcast(
|
|
62
|
+
"refresh_git_repository", repository_pk=repository.pk, head=repository.current_head
|
|
63
|
+
)
|
|
64
|
+
if job_result.duration:
|
|
65
|
+
self.logger.info("Repository synchronization completed in %s", job_result.duration)
|
|
66
|
+
except Exception:
|
|
67
|
+
job_result.log("Changes to database records have been reverted.")
|
|
68
|
+
# Re-check-out previous commit if any
|
|
69
|
+
repository.refresh_from_db()
|
|
70
|
+
if repository.current_head:
|
|
71
|
+
job_result.log(f"Attempting to revert local repository clone to commit {repository.current_head}")
|
|
72
|
+
ensure_git_repository(repository, logger=self.logger, head=repository.current_head)
|
|
73
|
+
refresh_job_code_from_repository(repository.slug, ignore_import_errors=True)
|
|
74
|
+
raise
|
|
59
75
|
|
|
60
76
|
|
|
61
77
|
class GitRepositoryDryRun(Job):
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from django.db import NotSupportedError
|
|
2
|
-
from django.db.models import Aggregate, Func, JSONField
|
|
2
|
+
from django.db.models import Aggregate, Func, JSONField, Value
|
|
3
|
+
from django.db.models.fields.json import compile_json_path
|
|
4
|
+
from django.db.models.functions import Cast
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class CollateAsChar(Func):
|
|
@@ -26,6 +28,150 @@ class CollateAsChar(Func):
|
|
|
26
28
|
return super().as_sql(compiler, connection, function, template, arg_joiner, **extra_context)
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
class JSONSet(Func):
|
|
32
|
+
"""
|
|
33
|
+
Set or create the value of a single key in a JSONField.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
model.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "cf_key", "new_value"))
|
|
37
|
+
|
|
38
|
+
Limitations:
|
|
39
|
+
- Postgres and MySQL only.
|
|
40
|
+
- Does *not* support nested lookups (`key1__key2`), only a single top-level key.
|
|
41
|
+
- Unlike the referenced Django PR, supports only a single key/value rather than an arbitrary number of them.
|
|
42
|
+
|
|
43
|
+
References:
|
|
44
|
+
- https://code.djangoproject.com/ticket/32519
|
|
45
|
+
- https://github.com/django/django/pull/18489/files
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
function = None
|
|
49
|
+
|
|
50
|
+
def __init__(self, expression, path, value, output_field=None):
|
|
51
|
+
self.path = path
|
|
52
|
+
self.value = value
|
|
53
|
+
super().__init__(expression, output_field=output_field)
|
|
54
|
+
|
|
55
|
+
def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
|
|
56
|
+
"""
|
|
57
|
+
Based on https://github.com/django/django/pull/18489/files.
|
|
58
|
+
|
|
59
|
+
Transforms and inserts self.path and self.value appropriately into the expression fields.
|
|
60
|
+
"""
|
|
61
|
+
c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
|
62
|
+
# Resolve expressions in the JSON update values.
|
|
63
|
+
c.fields = {
|
|
64
|
+
self.path: (
|
|
65
|
+
self.value.resolve_expression(query, allow_joins, reuse, summarize, for_save)
|
|
66
|
+
if hasattr(self.value, "resolve_expression")
|
|
67
|
+
else self.value
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
return c
|
|
71
|
+
|
|
72
|
+
def as_sql(self, compiler, connection, function=None, **extra_context):
|
|
73
|
+
"""
|
|
74
|
+
MySQL implementation based on https://github.com/django/django/pull/18489/files.
|
|
75
|
+
|
|
76
|
+
Creates a copy of this object with the appropriately transformed self.path and self.value for MySQL JSON_SET().
|
|
77
|
+
"""
|
|
78
|
+
if connection.vendor != "mysql":
|
|
79
|
+
raise NotSupportedError(f"JSONSet is not implemented for database {connection.vendor}")
|
|
80
|
+
|
|
81
|
+
copy = self.copy()
|
|
82
|
+
new_source_expressions = copy.get_source_expressions()
|
|
83
|
+
|
|
84
|
+
path = compile_json_path([self.path])
|
|
85
|
+
value = self.value
|
|
86
|
+
if not hasattr(value, "resolve_expression"):
|
|
87
|
+
# Use Value to serialize the value to a string, then Cast to ensure it's treated as JSON.
|
|
88
|
+
value = Cast(Value(value, output_field=self.output_field), output_field=self.output_field)
|
|
89
|
+
|
|
90
|
+
new_source_expressions.extend((Value(path), value))
|
|
91
|
+
copy.set_source_expressions(new_source_expressions)
|
|
92
|
+
return super(JSONSet, copy).as_sql(compiler, connection, function="JSON_SET", **extra_context)
|
|
93
|
+
|
|
94
|
+
def as_postgresql(self, compiler, connection, function=None, **extra_context):
|
|
95
|
+
"""
|
|
96
|
+
PostgreSQL implementation based on https://github.com/django/django/pull/18489/files.
|
|
97
|
+
|
|
98
|
+
Creates a copy of this object with appropriately transformed self.path and self.value for Postgres JSONB_SET().
|
|
99
|
+
"""
|
|
100
|
+
copy = self.copy()
|
|
101
|
+
new_source_expressions = copy.get_source_expressions()
|
|
102
|
+
|
|
103
|
+
path = self.path
|
|
104
|
+
value = self.value
|
|
105
|
+
if not hasattr(value, "resolve_expression"):
|
|
106
|
+
# We don't need Cast() here because Value with a JSONFIeld is correctly handled as JSONB by Postgres
|
|
107
|
+
value = Value(value, output_field=self.output_field)
|
|
108
|
+
else:
|
|
109
|
+
|
|
110
|
+
class ToJSONB(Func):
|
|
111
|
+
function = "TO_JSONB"
|
|
112
|
+
|
|
113
|
+
value = ToJSONB(value, output_field=self.output_field)
|
|
114
|
+
|
|
115
|
+
new_source_expressions.extend((Value(f"{{{path}}}"), value))
|
|
116
|
+
copy.set_source_expressions(new_source_expressions)
|
|
117
|
+
return super(JSONSet, copy).as_sql(compiler, connection, function="JSONB_SET", **extra_context)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class JSONRemove(Func):
|
|
121
|
+
"""
|
|
122
|
+
Unset and remove a single key in a JSONField.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
model.objects.all().update(_custom_field_data=JSONRemove("_custom_field_data", "cf_key"))
|
|
126
|
+
|
|
127
|
+
Limitations:
|
|
128
|
+
- Postgres and MySQL only.
|
|
129
|
+
- Does *not* support nested lookups (`key1__key2`), only a single top-level key.
|
|
130
|
+
- Unlike the referenced Django PR, supports only a single key, not N keys.
|
|
131
|
+
|
|
132
|
+
References:
|
|
133
|
+
- https://code.djangoproject.com/ticket/32519
|
|
134
|
+
- https://github.com/django/django/pull/18489/files
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, expression, path):
|
|
138
|
+
self.path = path
|
|
139
|
+
super().__init__(expression)
|
|
140
|
+
|
|
141
|
+
def as_sql(self, compiler, connection, function=None, **extra_context):
|
|
142
|
+
"""
|
|
143
|
+
MySQL implementation based on https://github.com/django/django/pull/18489/files.
|
|
144
|
+
|
|
145
|
+
Creates a copy of this object with appropriately transformed self.path for MySQL JSON_REMOVE().
|
|
146
|
+
"""
|
|
147
|
+
if connection.vendor != "mysql":
|
|
148
|
+
raise NotSupportedError(f"JSONSet is not implemented for database {connection.vendor}")
|
|
149
|
+
|
|
150
|
+
copy = self.copy()
|
|
151
|
+
new_source_expressions = copy.get_source_expressions()
|
|
152
|
+
|
|
153
|
+
new_source_expressions.append(Value(compile_json_path([self.path])))
|
|
154
|
+
|
|
155
|
+
copy.set_source_expressions(new_source_expressions)
|
|
156
|
+
return super(JSONRemove, copy).as_sql(compiler, connection, function="JSON_REMOVE", **extra_context)
|
|
157
|
+
|
|
158
|
+
def as_postgresql(self, compiler, connection, function=None, **extra_context):
|
|
159
|
+
"""
|
|
160
|
+
PostgreSQL implementation based on https://github.com/django/django/pull/18489/files.
|
|
161
|
+
|
|
162
|
+
Creates a copy of this object with appropriately transformed self.path for Postgres `#-` operator.
|
|
163
|
+
"""
|
|
164
|
+
copy = self.copy()
|
|
165
|
+
new_source_expressions = copy.get_source_expressions()
|
|
166
|
+
|
|
167
|
+
new_source_expressions.append(Value(f"{{{self.path}}}"))
|
|
168
|
+
|
|
169
|
+
copy.set_source_expressions(new_source_expressions)
|
|
170
|
+
return super(JSONRemove, copy).as_sql(
|
|
171
|
+
compiler, connection, template="%(expressions)s", arg_joiner="#- ", **extra_context
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
29
175
|
class JSONBAgg(Aggregate):
|
|
30
176
|
"""
|
|
31
177
|
Like django.contrib.postgres.aggregates.JSONBAgg, but different.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from django.core.cache import cache
|
|
2
2
|
from django.db.models import Case, When
|
|
3
|
+
from django.db.models.signals import post_delete, post_save
|
|
3
4
|
from tree_queries.models import TreeNode
|
|
4
5
|
from tree_queries.query import TreeManager as TreeManager_, TreeQuerySet as TreeQuerySet_
|
|
5
6
|
|
|
6
7
|
from nautobot.core.models import BaseManager, querysets
|
|
8
|
+
from nautobot.core.signals import invalidate_max_depth_cache
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
|
|
@@ -117,3 +119,9 @@ class TreeModel(TreeNode):
|
|
|
117
119
|
display_str += self.name
|
|
118
120
|
cache.set(cache_key, display_str, 5)
|
|
119
121
|
return display_str
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def __init_subclass__(cls, **kwargs):
|
|
125
|
+
super().__init_subclass__(**kwargs)
|
|
126
|
+
post_save.connect(invalidate_max_depth_cache, sender=cls)
|
|
127
|
+
post_delete.connect(invalidate_max_depth_cache, sender=cls)
|
nautobot/core/settings.py
CHANGED
|
@@ -418,7 +418,14 @@ if "NAUTOBOT_ALLOWED_HOSTS" in os.environ and os.environ["NAUTOBOT_ALLOWED_HOSTS
|
|
|
418
418
|
ALLOWED_HOSTS = os.environ["NAUTOBOT_ALLOWED_HOSTS"].split(" ")
|
|
419
419
|
else:
|
|
420
420
|
ALLOWED_HOSTS = []
|
|
421
|
+
|
|
422
|
+
# Allow CSRF trusted origins to be set via an environment variable
|
|
423
|
+
# This allows Nautobot to be run behind a reverse proxy that terminates TLS
|
|
424
|
+
# and fix potential issues with CSRF validation
|
|
421
425
|
CSRF_TRUSTED_ORIGINS = []
|
|
426
|
+
if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
|
|
427
|
+
CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
|
|
428
|
+
|
|
422
429
|
CSRF_FAILURE_VIEW = "nautobot.core.views.csrf_failure"
|
|
423
430
|
DATE_FORMAT = os.getenv("NAUTOBOT_DATE_FORMAT", "N j, Y")
|
|
424
431
|
DATETIME_FORMAT = os.getenv("NAUTOBOT_DATETIME_FORMAT", "N j, Y g:i a")
|
nautobot/core/settings.yaml
CHANGED
|
@@ -587,6 +587,16 @@ properties:
|
|
|
587
587
|
description: >-
|
|
588
588
|
A list of hosts (fully-qualified domain names (FQDNs) or subdomains) that are considered trusted origins
|
|
589
589
|
for cross-site secure requests such as HTTPS POST.
|
|
590
|
+
|
|
591
|
+
You might need to set this if you are using a reverse proxy or load balancer that changes the host header or if you face the error
|
|
592
|
+
'Invalid Form Submission - CSRF failure has occured' and 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
|
|
593
|
+
|
|
594
|
+
Example:
|
|
595
|
+
|
|
596
|
+
```python
|
|
597
|
+
CSRF_TRUSTED_ORIGINS = ['https://subdomain.example.com', 'https://*.example.com']
|
|
598
|
+
```
|
|
599
|
+
environment_variable: "NAUTOBOT_CSRF_TRUSTED_ORIGINS"
|
|
590
600
|
items:
|
|
591
601
|
type: "string"
|
|
592
602
|
see_also:
|
nautobot/core/signals.py
CHANGED
|
@@ -7,7 +7,6 @@ import logging
|
|
|
7
7
|
|
|
8
8
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
|
9
9
|
from django.core.cache import cache
|
|
10
|
-
from django.db.models.signals import post_delete, post_save
|
|
11
10
|
from django.dispatch import receiver, Signal
|
|
12
11
|
import redis.exceptions
|
|
13
12
|
|
|
@@ -62,10 +61,12 @@ def disable_for_loaddata(signal_handler):
|
|
|
62
61
|
return wrapper
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
@receiver(post_save)
|
|
66
|
-
@receiver(post_delete)
|
|
67
64
|
def invalidate_max_depth_cache(sender, **kwargs):
|
|
68
|
-
"""
|
|
65
|
+
"""
|
|
66
|
+
Clear the appropriate TreeManager.max_depth cache as the create/update/delete may have changed the tree.
|
|
67
|
+
|
|
68
|
+
Note that this signal is connected in `TreeModel.__init_subclass__()` so as to only apply to those models.
|
|
69
|
+
"""
|
|
69
70
|
from nautobot.core.models.tree_queries import TreeManager
|
|
70
71
|
|
|
71
72
|
if not isinstance(sender.objects, TreeManager):
|
|
@@ -94,9 +94,13 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
|
|
|
94
94
|
# FQDNs that are considered trusted origins for secure, cross-domain, requests such as HTTPS POST.
|
|
95
95
|
# If running Nautobot under a single domain, you may not need to set this variable;
|
|
96
96
|
# if running on multiple domains, you *may* need to set this variable to more or less the same as ALLOWED_HOSTS above.
|
|
97
|
+
# You also want to set this variable if you are facing CSRF validation issues such as
|
|
98
|
+
# 'CSRF failure has occured' or 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
|
|
97
99
|
# https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins
|
|
98
100
|
#
|
|
99
101
|
# CSRF_TRUSTED_ORIGINS = []
|
|
102
|
+
# if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
|
|
103
|
+
# CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
|
|
100
104
|
|
|
101
105
|
# Date/time formatting. See the following link for supported formats:
|
|
102
106
|
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from nautobot.core.models.query_functions import JSONRemove, JSONSet
|
|
2
|
+
from nautobot.core.testing import TestCase
|
|
3
|
+
from nautobot.dcim.models import Manufacturer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JSONFuncTests(TestCase):
|
|
7
|
+
"""Test JSONSet and JSONRemove functionality."""
|
|
8
|
+
|
|
9
|
+
def test_json_set(self):
|
|
10
|
+
# Setting a key/value should efficiently work
|
|
11
|
+
with self.assertNumQueries(1):
|
|
12
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "a", 1))
|
|
13
|
+
for mfr in Manufacturer.objects.all():
|
|
14
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
15
|
+
self.assertEqual(1, mfr._custom_field_data["a"])
|
|
16
|
+
|
|
17
|
+
# Setting a different key/value shouldn't overwrite other keys
|
|
18
|
+
with self.assertNumQueries(1):
|
|
19
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "b", "text"))
|
|
20
|
+
for mfr in Manufacturer.objects.all():
|
|
21
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
22
|
+
self.assertEqual(1, mfr._custom_field_data["a"])
|
|
23
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
24
|
+
self.assertEqual("text", mfr._custom_field_data["b"])
|
|
25
|
+
|
|
26
|
+
# Setting a key/value again should overwrite that value only
|
|
27
|
+
with self.assertNumQueries(1):
|
|
28
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "b", "more text"))
|
|
29
|
+
for mfr in Manufacturer.objects.all():
|
|
30
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
31
|
+
self.assertEqual(1, mfr._custom_field_data["a"])
|
|
32
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
33
|
+
self.assertEqual("more text", mfr._custom_field_data["b"])
|
|
34
|
+
|
|
35
|
+
# A filtered query should be updatable
|
|
36
|
+
with self.assertNumQueries(1):
|
|
37
|
+
Manufacturer.objects.filter(name__istartswith="a").update(
|
|
38
|
+
_custom_field_data=JSONSet("_custom_field_data", "a", None)
|
|
39
|
+
)
|
|
40
|
+
for mfr in Manufacturer.objects.filter(name__istartswith="a"):
|
|
41
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
42
|
+
self.assertEqual(None, mfr._custom_field_data["a"])
|
|
43
|
+
for mfr in Manufacturer.objects.exclude(name__istartswith="a"):
|
|
44
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
45
|
+
self.assertEqual(1, mfr._custom_field_data["a"])
|
|
46
|
+
for mfr in Manufacturer.objects.all():
|
|
47
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
48
|
+
self.assertEqual("more text", mfr._custom_field_data["b"])
|
|
49
|
+
|
|
50
|
+
# Setting a value doesn't require all existing values to be homogeneous
|
|
51
|
+
with self.assertNumQueries(1):
|
|
52
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "a", "hello"))
|
|
53
|
+
for mfr in Manufacturer.objects.all():
|
|
54
|
+
self.assertIn("a", mfr._custom_field_data)
|
|
55
|
+
self.assertEqual("hello", mfr._custom_field_data["a"])
|
|
56
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
57
|
+
self.assertEqual("more text", mfr._custom_field_data["b"])
|
|
58
|
+
|
|
59
|
+
def test_json_remove(self):
|
|
60
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONSet("_custom_field_data", "a", 1))
|
|
61
|
+
Manufacturer.objects.filter(name__istartswith="a").update(
|
|
62
|
+
_custom_field_data=JSONSet("_custom_field_data", "b", "hello")
|
|
63
|
+
)
|
|
64
|
+
Manufacturer.objects.exclude(name__istartswith="a").update(
|
|
65
|
+
_custom_field_data=JSONSet("_custom_field_data", "b", "world")
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Should be able to clear all values for a key without impacting other keys
|
|
69
|
+
with self.assertNumQueries(1):
|
|
70
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONRemove("_custom_field_data", "a"))
|
|
71
|
+
for mfr in Manufacturer.objects.all():
|
|
72
|
+
self.assertNotIn("a", mfr._custom_field_data)
|
|
73
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
74
|
+
for mfr in Manufacturer.objects.filter(name__istartswith="a"):
|
|
75
|
+
self.assertEqual("hello", mfr._custom_field_data["b"])
|
|
76
|
+
for mfr in Manufacturer.objects.exclude(name__istartswith="a"):
|
|
77
|
+
self.assertEqual("world", mfr._custom_field_data["b"])
|
|
78
|
+
|
|
79
|
+
# Clearing a value that doesn't exist should be safe
|
|
80
|
+
with self.assertNumQueries(1):
|
|
81
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONRemove("_custom_field_data", "a"))
|
|
82
|
+
for mfr in Manufacturer.objects.all():
|
|
83
|
+
self.assertNotIn("a", mfr._custom_field_data)
|
|
84
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
85
|
+
for mfr in Manufacturer.objects.filter(name__istartswith="a"):
|
|
86
|
+
self.assertEqual("hello", mfr._custom_field_data["b"])
|
|
87
|
+
for mfr in Manufacturer.objects.exclude(name__istartswith="a"):
|
|
88
|
+
self.assertEqual("world", mfr._custom_field_data["b"])
|
|
89
|
+
|
|
90
|
+
# Subsets should be updateable
|
|
91
|
+
with self.assertNumQueries(1):
|
|
92
|
+
Manufacturer.objects.filter(name__istartswith="a").update(
|
|
93
|
+
_custom_field_data=JSONRemove("_custom_field_data", "b")
|
|
94
|
+
)
|
|
95
|
+
for mfr in Manufacturer.objects.all():
|
|
96
|
+
self.assertNotIn("a", mfr._custom_field_data)
|
|
97
|
+
for mfr in Manufacturer.objects.filter(name__istartswith="a"):
|
|
98
|
+
self.assertNotIn("b", mfr._custom_field_data)
|
|
99
|
+
for mfr in Manufacturer.objects.exclude(name__istartswith="a"):
|
|
100
|
+
self.assertIn("b", mfr._custom_field_data)
|
|
101
|
+
self.assertEqual("world", mfr._custom_field_data["b"])
|
|
102
|
+
|
|
103
|
+
# Non-homogeneous data should be updatable
|
|
104
|
+
with self.assertNumQueries(1):
|
|
105
|
+
Manufacturer.objects.all().update(_custom_field_data=JSONRemove("_custom_field_data", "b"))
|
|
106
|
+
for mfr in Manufacturer.objects.all():
|
|
107
|
+
self.assertNotIn("a", mfr._custom_field_data)
|
|
108
|
+
self.assertNotIn("b", mfr._custom_field_data)
|
nautobot/dcim/forms.py
CHANGED
|
@@ -13,6 +13,8 @@ from nautobot.core.forms import (
|
|
|
13
13
|
add_blank_choice,
|
|
14
14
|
APISelect,
|
|
15
15
|
APISelectMultiple,
|
|
16
|
+
AutoPositionField,
|
|
17
|
+
AutoPositionPatternField,
|
|
16
18
|
BootstrapMixin,
|
|
17
19
|
BulkEditNullBooleanSelect,
|
|
18
20
|
ColorSelect,
|
|
@@ -269,23 +271,7 @@ class ComponentForm(BootstrapMixin, forms.Form):
|
|
|
269
271
|
|
|
270
272
|
|
|
271
273
|
class ModularComponentForm(ComponentForm):
|
|
272
|
-
|
|
273
|
-
label="Name",
|
|
274
|
-
help_text="""
|
|
275
|
-
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
276
|
-
are not supported. Examples:
|
|
277
|
-
<ul>
|
|
278
|
-
<li><code>[ge,xe]-0/0/[0-9]</code></li>
|
|
279
|
-
<li><code>e[0-3][a-d,f]</code></li>
|
|
280
|
-
</ul>
|
|
281
|
-
|
|
282
|
-
The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
|
|
283
|
-
may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
|
|
284
|
-
module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
|
|
285
|
-
multiple times in the component name and there is no limit to the depth of parent levels.
|
|
286
|
-
Any variables that cannot be replaced by a suitable position value will remain unchanged.
|
|
287
|
-
""",
|
|
288
|
-
)
|
|
274
|
+
"""Base class for forms for components that can be assigned to either a device or a module."""
|
|
289
275
|
|
|
290
276
|
|
|
291
277
|
#
|
|
@@ -1064,11 +1050,27 @@ class ComponentTemplateCreateForm(ComponentForm):
|
|
|
1064
1050
|
description = forms.CharField(required=False)
|
|
1065
1051
|
|
|
1066
1052
|
|
|
1067
|
-
class ModularComponentTemplateCreateForm(
|
|
1053
|
+
class ModularComponentTemplateCreateForm(ComponentTemplateCreateForm):
|
|
1068
1054
|
"""
|
|
1069
1055
|
Base form for the creation of modular device component templates (subclassed from ModularComponentTemplateModel).
|
|
1070
1056
|
"""
|
|
1071
1057
|
|
|
1058
|
+
name_pattern = ExpandableNameField(
|
|
1059
|
+
label="Name",
|
|
1060
|
+
help_text="""
|
|
1061
|
+
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
1062
|
+
are not supported. Examples:
|
|
1063
|
+
<ul>
|
|
1064
|
+
<li><code>[ge,xe]-0/0/[0-9]</code></li>
|
|
1065
|
+
<li><code>e[0-3][a-d,f]</code></li>
|
|
1066
|
+
</ul>
|
|
1067
|
+
|
|
1068
|
+
The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
|
|
1069
|
+
may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
|
|
1070
|
+
module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
|
|
1071
|
+
multiple times in the component name and there is no limit to the depth of parent levels.
|
|
1072
|
+
Any variables that cannot be replaced by a suitable position value will remain unchanged.""",
|
|
1073
|
+
)
|
|
1072
1074
|
device_type = DynamicModelChoiceField(
|
|
1073
1075
|
queryset=DeviceType.objects.all(),
|
|
1074
1076
|
required=False,
|
|
@@ -1077,7 +1079,6 @@ class ModularComponentTemplateCreateForm(ModularComponentForm):
|
|
|
1077
1079
|
queryset=ModuleType.objects.all(),
|
|
1078
1080
|
required=False,
|
|
1079
1081
|
)
|
|
1080
|
-
description = forms.CharField(required=False)
|
|
1081
1082
|
|
|
1082
1083
|
|
|
1083
1084
|
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
|
@@ -1570,10 +1571,10 @@ class ModuleBayBaseCreateForm(BootstrapMixin, forms.Form):
|
|
|
1570
1571
|
required=False,
|
|
1571
1572
|
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
|
|
1572
1573
|
)
|
|
1573
|
-
position_pattern =
|
|
1574
|
-
label="Position",
|
|
1574
|
+
position_pattern = AutoPositionPatternField(
|
|
1575
1575
|
required=False,
|
|
1576
|
-
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)"
|
|
1576
|
+
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)"
|
|
1577
|
+
" Default to the names of the module bays unless manually supplied by the user.",
|
|
1577
1578
|
)
|
|
1578
1579
|
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
1579
1580
|
|
|
@@ -2542,12 +2543,8 @@ class DeviceBulkAddComponentForm(ComponentForm, CustomFieldModelBulkEditFormMixi
|
|
|
2542
2543
|
nullable_fields = []
|
|
2543
2544
|
|
|
2544
2545
|
|
|
2545
|
-
class ModuleBulkAddComponentForm(
|
|
2546
|
+
class ModuleBulkAddComponentForm(DeviceBulkAddComponentForm):
|
|
2546
2547
|
pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
|
|
2547
|
-
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
2548
|
-
|
|
2549
|
-
class Meta:
|
|
2550
|
-
nullable_fields = []
|
|
2551
2548
|
|
|
2552
2549
|
|
|
2553
2550
|
#
|
|
@@ -3566,6 +3563,12 @@ class ModuleBayFilterForm(NautobotFilterForm):
|
|
|
3566
3563
|
|
|
3567
3564
|
|
|
3568
3565
|
class ModuleBayForm(NautobotModelForm):
|
|
3566
|
+
position = AutoPositionField(
|
|
3567
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
3568
|
+
help_text="The position of the module bay within the parent device/module. "
|
|
3569
|
+
"Defaults to the name of the module bay unless overridden.",
|
|
3570
|
+
required=False,
|
|
3571
|
+
)
|
|
3569
3572
|
parent_device = DynamicModelChoiceField(
|
|
3570
3573
|
queryset=Device.objects.all(),
|
|
3571
3574
|
required=False,
|
|
@@ -1280,3 +1280,8 @@ class ModuleBay(PrimaryModel):
|
|
|
1280
1280
|
|
|
1281
1281
|
if not (self.parent_device or self.parent_module):
|
|
1282
1282
|
raise ValidationError("Either parent_device or parent_module must be set")
|
|
1283
|
+
|
|
1284
|
+
# Populate the position field with the name of the module bay if it is not supplied by the user.
|
|
1285
|
+
|
|
1286
|
+
if not self.position:
|
|
1287
|
+
self.position = self.name
|
nautobot/dcim/tables/devices.py
CHANGED
|
@@ -173,6 +173,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
173
173
|
template_code="""{% if record.device_redundancy_group %}<span class="badge badge-default">{{ record.device_redundancy_group_priority|default:'None' }}</span>{% else %}—{% endif %}"""
|
|
174
174
|
)
|
|
175
175
|
controller_managed_device_group = tables.Column(linkify=True)
|
|
176
|
+
software_version = tables.Column(linkify=True, verbose_name="Software Version")
|
|
176
177
|
secrets_group = tables.Column(linkify=True)
|
|
177
178
|
tags = TagColumn(url_name="dcim:device_list")
|
|
178
179
|
|
|
@@ -201,6 +202,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
201
202
|
"vc_priority",
|
|
202
203
|
"device_redundancy_group",
|
|
203
204
|
"device_redundancy_group_priority",
|
|
205
|
+
"software_version",
|
|
204
206
|
"controller_managed_device_group",
|
|
205
207
|
"secrets_group",
|
|
206
208
|
"tags",
|
|
@@ -616,7 +618,7 @@ class DeviceModulePowerOutletTable(PowerOutletTable):
|
|
|
616
618
|
row_attrs = {"class": cable_status_color_css}
|
|
617
619
|
|
|
618
620
|
|
|
619
|
-
class BaseInterfaceTable(BaseTable):
|
|
621
|
+
class BaseInterfaceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
620
622
|
enabled = BooleanColumn()
|
|
621
623
|
ip_addresses = tables.TemplateColumn(
|
|
622
624
|
template_code=INTERFACE_IPADDRESSES,
|
|
@@ -632,7 +634,7 @@ class BaseInterfaceTable(BaseTable):
|
|
|
632
634
|
vrf = tables.Column(linkify=True, verbose_name="VRF")
|
|
633
635
|
|
|
634
636
|
|
|
635
|
-
class InterfaceTable(
|
|
637
|
+
class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
|
|
636
638
|
mgmt_only = BooleanColumn()
|
|
637
639
|
tags = TagColumn(url_name="dcim:interface_list")
|
|
638
640
|
|