nautobot 2.4.6__py3-none-any.whl → 2.4.7__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/forms.py +2 -0
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +2 -55
- nautobot/circuits/views.py +20 -23
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +2 -40
- nautobot/cloud/views.py +12 -0
- nautobot/core/api/urls.py +2 -2
- nautobot/core/api/views.py +39 -1
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/fields.py +2 -2
- nautobot/core/forms/widgets.py +18 -0
- nautobot/core/templates/widgets/sluginput.html +5 -1
- nautobot/core/templatetags/helpers.py +15 -1
- nautobot/core/testing/integration.py +6 -2
- nautobot/core/ui/object_detail.py +16 -3
- nautobot/core/utils/lookup.py +2 -2
- nautobot/dcim/forms.py +10 -0
- nautobot/dcim/models/locations.py +9 -0
- nautobot/dcim/templates/dcim/device_list.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -63
- nautobot/dcim/templates/dcim/module_list.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +1 -1
- nautobot/dcim/tests/integration/test_module_bay_position.py +125 -0
- nautobot/dcim/tests/test_models.py +13 -0
- nautobot/dcim/tests/test_views.py +4 -1
- nautobot/dcim/urls.py +1 -45
- nautobot/dcim/views.py +35 -66
- nautobot/extras/choices.py +4 -0
- nautobot/extras/filters/__init__.py +6 -0
- nautobot/extras/forms/forms.py +76 -8
- nautobot/extras/models/customfields.py +10 -9
- nautobot/extras/signals.py +43 -4
- nautobot/extras/tasks.py +4 -2
- nautobot/extras/templates/extras/contact_retrieve.html +1 -58
- nautobot/extras/templates/extras/exporttemplate.html +1 -53
- nautobot/extras/templates/extras/team_retrieve.html +1 -58
- nautobot/extras/tests/test_customfields.py +24 -0
- nautobot/extras/tests/test_views.py +22 -0
- nautobot/extras/urls.py +2 -70
- nautobot/extras/views.py +101 -79
- nautobot/ipam/tables.py +1 -0
- nautobot/project-static/css/base.css +5 -0
- nautobot/project-static/docs/404.html +0 -2
- nautobot/project-static/docs/apps/index.html +0 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +62 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +7 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +0 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +0 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +0 -2
- nautobot/project-static/docs/development/apps/api/setup.html +0 -2
- nautobot/project-static/docs/development/apps/api/testing.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +0 -2
- nautobot/project-static/docs/development/apps/index.html +0 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +0 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +0 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +0 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +0 -2
- nautobot/project-static/docs/development/core/application-registry.html +0 -2
- nautobot/project-static/docs/development/core/best-practices.html +0 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +0 -2
- nautobot/project-static/docs/development/core/caching.html +0 -2
- nautobot/project-static/docs/development/core/controllers.html +0 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +0 -2
- nautobot/project-static/docs/development/core/generic-views.html +0 -2
- nautobot/project-static/docs/development/core/getting-started.html +0 -2
- nautobot/project-static/docs/development/core/homepage.html +0 -2
- nautobot/project-static/docs/development/core/index.html +0 -2
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +0 -2
- nautobot/project-static/docs/development/core/model-checklist.html +0 -2
- nautobot/project-static/docs/development/core/model-features.html +0 -2
- nautobot/project-static/docs/development/core/natural-keys.html +0 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +0 -2
- nautobot/project-static/docs/development/core/release-checklist.html +0 -2
- nautobot/project-static/docs/development/core/role-internals.html +0 -2
- nautobot/project-static/docs/development/core/settings.html +0 -2
- nautobot/project-static/docs/development/core/style-guide.html +0 -2
- nautobot/project-static/docs/development/core/templates.html +0 -2
- nautobot/project-static/docs/development/core/testing.html +0 -2
- nautobot/project-static/docs/development/core/ui-component-framework.html +0 -2
- nautobot/project-static/docs/development/core/user-preferences.html +0 -2
- nautobot/project-static/docs/development/index.html +0 -2
- nautobot/project-static/docs/development/jobs/index.html +0 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +0 -2
- nautobot/project-static/docs/index.html +0 -2
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +0 -2
- nautobot/project-static/docs/overview/design_philosophy.html +0 -2
- nautobot/project-static/docs/release-notes/index.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.3.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.4.html +138 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +0 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +0 -2
- nautobot/project-static/docs/user-guide/administration/security/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/security/notices.html +0 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +0 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +0 -2
- nautobot/project-static/docs/user-guide/index.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -2
- nautobot/project-static/js/forms.js +88 -37
- nautobot/project-static/js/homepage_layout.js +12 -3
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/METADATA +1 -1
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/RECORD +346 -346
- nautobot/dcim/templates/dcim/modulebay_create.html +0 -39
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/NOTICE +0 -0
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/WHEEL +0 -0
- {nautobot-2.4.6.dist-info → nautobot-2.4.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from django.urls import reverse
|
|
2
|
+
|
|
3
|
+
from nautobot.core.testing.integration import ObjectsListMixin, SeleniumTestCase
|
|
4
|
+
from nautobot.dcim.models import DeviceType, Manufacturer, Module, ModuleBay, ModuleType
|
|
5
|
+
from nautobot.extras.models import Status
|
|
6
|
+
from nautobot.extras.tests.integration import create_test_device
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ModuleBayPositionTestCase(SeleniumTestCase, ObjectsListMixin):
|
|
10
|
+
"""
|
|
11
|
+
Test creating a module bay component in device and device type.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def _validate_position_field(self):
|
|
15
|
+
# Fill name pattern
|
|
16
|
+
name_pattern_field = self.browser.find_by_css("#id_name_pattern")
|
|
17
|
+
name_pattern_value = "name-0/0/[0-9]"
|
|
18
|
+
for _ in name_pattern_field.type(name_pattern_value, slowly=True):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
# Verify that position is filled
|
|
22
|
+
position_field = self.browser.find_by_css("#id_position_pattern")
|
|
23
|
+
self.assertEqual(position_field.value, name_pattern_value, "Position field value is not properly set")
|
|
24
|
+
|
|
25
|
+
# Change pattern manually and name to verify if it's not updating then
|
|
26
|
+
position_field.fill("")
|
|
27
|
+
for _ in position_field.type("new pattern", slowly=True):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
for _ in name_pattern_field.type("v2", slowly=True):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
self.assertEqual(position_field.value, "new pattern", "Position field value has unexpectedly changed")
|
|
34
|
+
|
|
35
|
+
# Regenerate position
|
|
36
|
+
self.browser.find_by_css('button[data-original-title="Regenerate position"]').click()
|
|
37
|
+
self.assertEqual(position_field.value, f"{name_pattern_value}v2", "Position field value is not re-populated")
|
|
38
|
+
|
|
39
|
+
def test_create_device_type_module_bay(self):
|
|
40
|
+
self.login_as_superuser()
|
|
41
|
+
|
|
42
|
+
manufacturer, _ = Manufacturer.objects.get_or_create(
|
|
43
|
+
name="Test Manufacturer",
|
|
44
|
+
)
|
|
45
|
+
device_type, _ = DeviceType.objects.get_or_create(manufacturer=manufacturer, model="Test Model Module Bay")
|
|
46
|
+
|
|
47
|
+
details_url = self.live_server_url + reverse("dcim:devicetype", kwargs={"pk": device_type.pk})
|
|
48
|
+
self.browser.visit(details_url)
|
|
49
|
+
|
|
50
|
+
# Navigate to module bay create page
|
|
51
|
+
self.browser.find_by_css("#device-type-add-components-button").click()
|
|
52
|
+
self.browser.find_by_xpath(
|
|
53
|
+
"//*[@id='device-type-add-components-button']/following-sibling::*//a[normalize-space()='Module Bays']"
|
|
54
|
+
).click()
|
|
55
|
+
|
|
56
|
+
self._validate_position_field()
|
|
57
|
+
|
|
58
|
+
def test_create_device_module_bay(self):
|
|
59
|
+
self.login_as_superuser()
|
|
60
|
+
|
|
61
|
+
device = create_test_device("Test Device Module Bay Integration Test 1")
|
|
62
|
+
details_url = self.live_server_url + reverse("dcim:device", kwargs={"pk": device.pk})
|
|
63
|
+
self.browser.visit(details_url)
|
|
64
|
+
|
|
65
|
+
# Navigate to module bay create page
|
|
66
|
+
self.browser.find_by_css("#device-add-components-button").click()
|
|
67
|
+
self.browser.find_by_xpath(
|
|
68
|
+
"//*[@id='device-add-components-button']/following-sibling::*//a[normalize-space()='Module Bays']"
|
|
69
|
+
).click()
|
|
70
|
+
|
|
71
|
+
self._validate_position_field()
|
|
72
|
+
|
|
73
|
+
def test_bulk_create_device_module_bay(self):
|
|
74
|
+
self.login_as_superuser()
|
|
75
|
+
|
|
76
|
+
device = create_test_device("Test Device Module Bay Integration Test 1", test_uuid="a15a58b0b")
|
|
77
|
+
self.browser.visit(self.live_server_url + reverse("dcim:device_list"))
|
|
78
|
+
|
|
79
|
+
self.apply_filter("location", "a15a58b0b")
|
|
80
|
+
|
|
81
|
+
self.select_one_item(pk=device.pk)
|
|
82
|
+
self.browser.find_by_css("#device-bulk-add-components-button").click()
|
|
83
|
+
self.browser.find_by_xpath(
|
|
84
|
+
"//*[@id='device-bulk-add-components-button']/following-sibling::*//a[normalize-space()='Module Bays']"
|
|
85
|
+
).click()
|
|
86
|
+
|
|
87
|
+
self._validate_position_field()
|
|
88
|
+
|
|
89
|
+
def test_create_module_type_module_bay(self):
|
|
90
|
+
self.login_as_superuser()
|
|
91
|
+
|
|
92
|
+
manufacturer, _ = Manufacturer.objects.get_or_create(name="Test Manufacturer")
|
|
93
|
+
module_type = ModuleType.objects.create(model="Module_Type", manufacturer=manufacturer)
|
|
94
|
+
|
|
95
|
+
details_url = self.live_server_url + reverse("dcim:moduletype", kwargs={"pk": module_type.pk})
|
|
96
|
+
self.browser.visit(details_url)
|
|
97
|
+
|
|
98
|
+
self.browser.find_by_css("#module-type-add-components-button").click()
|
|
99
|
+
self.browser.find_by_xpath(
|
|
100
|
+
"//*[@id='module-type-add-components-button']/following-sibling::*//a[normalize-space()='Module Bays']"
|
|
101
|
+
).click()
|
|
102
|
+
|
|
103
|
+
self._validate_position_field()
|
|
104
|
+
|
|
105
|
+
def test_bulk_create_module_module_bay(self):
|
|
106
|
+
self.login_as_superuser()
|
|
107
|
+
|
|
108
|
+
device = create_test_device("Test Device Module Bay Integration Test 2", test_uuid="60a7d5e")
|
|
109
|
+
module_type = ModuleType.objects.create(model="Module_Type", manufacturer=device.device_type.manufacturer)
|
|
110
|
+
device_module_bay = ModuleBay.objects.create(parent_device=device, name="Test Bay")
|
|
111
|
+
module = Module.objects.create(
|
|
112
|
+
module_type=module_type,
|
|
113
|
+
status=Status.objects.get_for_model(Module).first(),
|
|
114
|
+
parent_module_bay=device_module_bay,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
self.browser.visit(self.live_server_url + reverse("dcim:module_list"))
|
|
118
|
+
self.select_one_item(pk=module.pk)
|
|
119
|
+
|
|
120
|
+
self.browser.find_by_css("#module-bulk-add-components-button").click()
|
|
121
|
+
self.browser.find_by_xpath(
|
|
122
|
+
"//*[@id='module-bulk-add-components-button']/following-sibling::*//a[normalize-space()='Module Bays']"
|
|
123
|
+
).click()
|
|
124
|
+
|
|
125
|
+
self._validate_position_field()
|
|
@@ -1338,6 +1338,19 @@ class LocationTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1338
1338
|
str(cm.exception),
|
|
1339
1339
|
)
|
|
1340
1340
|
|
|
1341
|
+
def test_default_treemodel_display(self):
|
|
1342
|
+
location_1 = Location(name="Building 1", location_type=self.root_type, status=self.status)
|
|
1343
|
+
location_1.validated_save()
|
|
1344
|
+
location_2 = Location(name="Room 1", location_type=self.leaf_type, parent=location_1, status=self.status)
|
|
1345
|
+
self.assertEqual(location_2.display, "Building 1 → Room 1")
|
|
1346
|
+
|
|
1347
|
+
@override_settings(LOCATION_NAME_AS_NATURAL_KEY=True)
|
|
1348
|
+
def test_location_name_as_natural_key_display(self):
|
|
1349
|
+
location_1 = Location(name="Building 1", location_type=self.root_type, status=self.status)
|
|
1350
|
+
location_1.validated_save()
|
|
1351
|
+
location_2 = Location(name="Room 1", location_type=self.leaf_type, parent=location_1, status=self.status)
|
|
1352
|
+
self.assertEqual(location_2.display, "Room 1")
|
|
1353
|
+
|
|
1341
1354
|
|
|
1342
1355
|
class PlatformTestCase(TestCase):
|
|
1343
1356
|
def setUp(self):
|
|
@@ -760,7 +760,7 @@ class DeviceFamilyTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
760
760
|
DeviceFamily.objects.create(name="Deletable Device Family 3")
|
|
761
761
|
|
|
762
762
|
|
|
763
|
-
class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
763
|
+
class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases.BulkEditObjectsViewTestCase):
|
|
764
764
|
model = Manufacturer
|
|
765
765
|
|
|
766
766
|
@classmethod
|
|
@@ -769,6 +769,9 @@ class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
769
769
|
"name": "Manufacturer X",
|
|
770
770
|
"description": "A new manufacturer",
|
|
771
771
|
}
|
|
772
|
+
cls.bulk_edit_data = {
|
|
773
|
+
"description": "Updated manufacturer description",
|
|
774
|
+
}
|
|
772
775
|
|
|
773
776
|
def get_deletable_object(self):
|
|
774
777
|
mf = Manufacturer.objects.create(name="Deletable Manufacturer")
|
nautobot/dcim/urls.py
CHANGED
|
@@ -17,7 +17,6 @@ from .models import (
|
|
|
17
17
|
Interface,
|
|
18
18
|
InventoryItem,
|
|
19
19
|
Location,
|
|
20
|
-
Manufacturer,
|
|
21
20
|
Platform,
|
|
22
21
|
PowerFeed,
|
|
23
22
|
PowerOutlet,
|
|
@@ -40,6 +39,7 @@ router.register("device-redundancy-groups", views.DeviceRedundancyGroupUIViewSet
|
|
|
40
39
|
router.register("interface-redundancy-groups", views.InterfaceRedundancyGroupUIViewSet)
|
|
41
40
|
router.register("interface-redundancy-groups-associations", views.InterfaceRedundancyGroupAssociationUIViewSet)
|
|
42
41
|
router.register("location-types", views.LocationTypeUIViewSet)
|
|
42
|
+
router.register("manufacturers", views.ManufacturerUIViewSet)
|
|
43
43
|
router.register("module-bays", views.ModuleBayUIViewSet)
|
|
44
44
|
router.register("module-bay-templates", views.ModuleBayTemplateUIViewSet)
|
|
45
45
|
router.register("modules", views.ModuleUIViewSet)
|
|
@@ -208,50 +208,6 @@ urlpatterns = [
|
|
|
208
208
|
name="rack_add_image",
|
|
209
209
|
kwargs={"model": Rack},
|
|
210
210
|
),
|
|
211
|
-
# Manufacturers
|
|
212
|
-
path("manufacturers/", views.ManufacturerListView.as_view(), name="manufacturer_list"),
|
|
213
|
-
path(
|
|
214
|
-
"manufacturers/add/",
|
|
215
|
-
views.ManufacturerEditView.as_view(),
|
|
216
|
-
name="manufacturer_add",
|
|
217
|
-
),
|
|
218
|
-
path(
|
|
219
|
-
"manufacturers/import/",
|
|
220
|
-
views.ManufacturerBulkImportView.as_view(), # 3.0 TODO: remove, unused
|
|
221
|
-
name="manufacturer_import",
|
|
222
|
-
),
|
|
223
|
-
path(
|
|
224
|
-
"manufacturers/delete/",
|
|
225
|
-
views.ManufacturerBulkDeleteView.as_view(),
|
|
226
|
-
name="manufacturer_bulk_delete",
|
|
227
|
-
),
|
|
228
|
-
path(
|
|
229
|
-
"manufacturers/<uuid:pk>/",
|
|
230
|
-
views.ManufacturerView.as_view(),
|
|
231
|
-
name="manufacturer",
|
|
232
|
-
),
|
|
233
|
-
path(
|
|
234
|
-
"manufacturers/<uuid:pk>/edit/",
|
|
235
|
-
views.ManufacturerEditView.as_view(),
|
|
236
|
-
name="manufacturer_edit",
|
|
237
|
-
),
|
|
238
|
-
path(
|
|
239
|
-
"manufacturers/<uuid:pk>/delete/",
|
|
240
|
-
views.ManufacturerDeleteView.as_view(),
|
|
241
|
-
name="manufacturer_delete",
|
|
242
|
-
),
|
|
243
|
-
path(
|
|
244
|
-
"manufacturers/<uuid:pk>/changelog/",
|
|
245
|
-
ObjectChangeLogView.as_view(),
|
|
246
|
-
name="manufacturer_changelog",
|
|
247
|
-
kwargs={"model": Manufacturer},
|
|
248
|
-
),
|
|
249
|
-
path(
|
|
250
|
-
"manufacturers/<uuid:pk>/notes/",
|
|
251
|
-
ObjectNotesView.as_view(),
|
|
252
|
-
name="manufacturer_notes",
|
|
253
|
-
kwargs={"model": Manufacturer},
|
|
254
|
-
),
|
|
255
211
|
# Device types
|
|
256
212
|
path("device-types/", views.DeviceTypeListView.as_view(), name="devicetype_list"),
|
|
257
213
|
path("device-types/add/", views.DeviceTypeEditView.as_view(), name="devicetype_add"),
|
nautobot/dcim/views.py
CHANGED
|
@@ -28,7 +28,6 @@ from rest_framework.exceptions import MethodNotAllowed
|
|
|
28
28
|
from rest_framework.response import Response
|
|
29
29
|
|
|
30
30
|
from nautobot.circuits.models import Circuit
|
|
31
|
-
from nautobot.cloud.models import CloudAccount
|
|
32
31
|
from nautobot.cloud.tables import CloudAccountTable
|
|
33
32
|
from nautobot.core.choices import ButtonColorChoices
|
|
34
33
|
from nautobot.core.exceptions import AbortTransaction
|
|
@@ -304,7 +303,7 @@ class LocationView(generic.ObjectView):
|
|
|
304
303
|
.select_related("parent", "location_type")
|
|
305
304
|
)
|
|
306
305
|
|
|
307
|
-
children_table = tables.LocationTable(children)
|
|
306
|
+
children_table = tables.LocationTable(children, hide_hierarchy_ui=True)
|
|
308
307
|
|
|
309
308
|
paginate = {
|
|
310
309
|
"paginator_class": EnhancedPaginator,
|
|
@@ -724,71 +723,40 @@ class RackReservationBulkDeleteView(generic.BulkDeleteView):
|
|
|
724
723
|
#
|
|
725
724
|
|
|
726
725
|
|
|
727
|
-
class
|
|
726
|
+
class ManufacturerUIViewSet(NautobotUIViewSet):
|
|
727
|
+
bulk_update_form_class = forms.ManufacturerBulkEditForm
|
|
728
|
+
filterset_class = filters.ManufacturerFilterSet
|
|
729
|
+
filterset_form_class = forms.ManufacturerFilterForm
|
|
730
|
+
form_class = forms.ManufacturerForm
|
|
731
|
+
serializer_class = serializers.ManufacturerSerializer
|
|
732
|
+
table_class = tables.ManufacturerTable
|
|
728
733
|
queryset = Manufacturer.objects.all()
|
|
729
|
-
filterset = filters.ManufacturerFilterSet
|
|
730
|
-
filterset_form = forms.ManufacturerFilterForm
|
|
731
|
-
table = tables.ManufacturerTable
|
|
732
734
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
.
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
cloud_account_table = CloudAccountTable(cloud_accounts)
|
|
761
|
-
paginate = {
|
|
762
|
-
"paginator_class": EnhancedPaginator,
|
|
763
|
-
"per_page": get_paginate_count(request),
|
|
764
|
-
}
|
|
765
|
-
RequestConfig(request, paginate).configure(cloud_account_table)
|
|
766
|
-
|
|
767
|
-
return {
|
|
768
|
-
"device_table": device_table,
|
|
769
|
-
"cloud_account_table": cloud_account_table,
|
|
770
|
-
**super().get_extra_context(request, instance),
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
class ManufacturerEditView(generic.ObjectEditView):
|
|
775
|
-
queryset = Manufacturer.objects.all()
|
|
776
|
-
model_form = forms.ManufacturerForm
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
class ManufacturerDeleteView(generic.ObjectDeleteView):
|
|
780
|
-
queryset = Manufacturer.objects.all()
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
class ManufacturerBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
|
|
784
|
-
queryset = Manufacturer.objects.all()
|
|
785
|
-
table = tables.ManufacturerTable
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
|
789
|
-
queryset = Manufacturer.objects.all()
|
|
790
|
-
table = tables.ManufacturerTable
|
|
791
|
-
filterset = filters.ManufacturerFilterSet
|
|
735
|
+
# Object detail content with devices and cloud accounts related to the manufacturer
|
|
736
|
+
object_detail_content = object_detail.ObjectDetailContent(
|
|
737
|
+
panels=(
|
|
738
|
+
object_detail.ObjectFieldsPanel(
|
|
739
|
+
weight=100,
|
|
740
|
+
section=SectionChoices.LEFT_HALF,
|
|
741
|
+
fields="__all__",
|
|
742
|
+
),
|
|
743
|
+
object_detail.ObjectsTablePanel(
|
|
744
|
+
weight=100,
|
|
745
|
+
section=SectionChoices.FULL_WIDTH,
|
|
746
|
+
table_class=tables.DeviceTable,
|
|
747
|
+
table_filter="device_type__manufacturer",
|
|
748
|
+
related_field_name="manufacturer",
|
|
749
|
+
exclude_columns=["manufacturer"],
|
|
750
|
+
),
|
|
751
|
+
object_detail.ObjectsTablePanel(
|
|
752
|
+
weight=100,
|
|
753
|
+
section=SectionChoices.FULL_WIDTH,
|
|
754
|
+
table_class=CloudAccountTable,
|
|
755
|
+
table_filter="provider",
|
|
756
|
+
exclude_columns=["provider"],
|
|
757
|
+
),
|
|
758
|
+
),
|
|
759
|
+
)
|
|
792
760
|
|
|
793
761
|
|
|
794
762
|
#
|
|
@@ -1769,6 +1737,7 @@ class DeviceView(generic.ObjectView):
|
|
|
1769
1737
|
weight=100,
|
|
1770
1738
|
color=ButtonColorChoices.BLUE,
|
|
1771
1739
|
label="Add Components",
|
|
1740
|
+
attributes={"id": "device-add-components-button"},
|
|
1772
1741
|
icon="mdi-plus-thick",
|
|
1773
1742
|
required_permissions=["dcim.change_device"],
|
|
1774
1743
|
children=(
|
|
@@ -3436,7 +3405,7 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
|
|
|
3436
3405
|
model_form_class = forms.ModuleBayForm
|
|
3437
3406
|
serializer_class = serializers.ModuleBaySerializer
|
|
3438
3407
|
table_class = tables.ModuleBayTable
|
|
3439
|
-
create_template_name = "dcim/
|
|
3408
|
+
create_template_name = "dcim/device_component_add.html"
|
|
3440
3409
|
|
|
3441
3410
|
def get_extra_context(self, request, instance):
|
|
3442
3411
|
if instance:
|
nautobot/extras/choices.py
CHANGED
|
@@ -488,6 +488,8 @@ class SecretsGroupSecretTypeChoices(ChoiceSet):
|
|
|
488
488
|
TYPE_SECRET = "secret" # noqa: S105 # hardcoded-password-string -- false positive
|
|
489
489
|
TYPE_TOKEN = "token" # noqa: S105 # hardcoded-password-string -- false positive
|
|
490
490
|
TYPE_USERNAME = "username"
|
|
491
|
+
TYPE_URL = "url"
|
|
492
|
+
TYPE_NOTES = "notes"
|
|
491
493
|
|
|
492
494
|
CHOICES = (
|
|
493
495
|
(TYPE_KEY, "Key"),
|
|
@@ -495,6 +497,8 @@ class SecretsGroupSecretTypeChoices(ChoiceSet):
|
|
|
495
497
|
(TYPE_SECRET, "Secret"),
|
|
496
498
|
(TYPE_TOKEN, "Token"),
|
|
497
499
|
(TYPE_USERNAME, "Username"),
|
|
500
|
+
(TYPE_URL, "URL"),
|
|
501
|
+
(TYPE_NOTES, "Notes"),
|
|
498
502
|
)
|
|
499
503
|
|
|
500
504
|
|
|
@@ -526,6 +526,12 @@ class ContactTeamFilterSet(NameSearchFilterSet, NautobotFilterSet):
|
|
|
526
526
|
|
|
527
527
|
|
|
528
528
|
class ContactFilterSet(ContactTeamFilterSet):
|
|
529
|
+
teams = NaturalKeyOrPKMultipleChoiceFilter(
|
|
530
|
+
queryset=Team.objects.all(),
|
|
531
|
+
to_field_name="name",
|
|
532
|
+
label="Team (name or ID)",
|
|
533
|
+
)
|
|
534
|
+
|
|
529
535
|
class Meta:
|
|
530
536
|
model = Contact
|
|
531
537
|
fields = "__all__"
|
nautobot/extras/forms/forms.py
CHANGED
|
@@ -142,6 +142,7 @@ __all__ = (
|
|
|
142
142
|
"DynamicGroupFilterForm",
|
|
143
143
|
"DynamicGroupForm",
|
|
144
144
|
"DynamicGroupMembershipFormSet",
|
|
145
|
+
"ExportTemplateBulkEditForm",
|
|
145
146
|
"ExportTemplateFilterForm",
|
|
146
147
|
"ExportTemplateForm",
|
|
147
148
|
"ExternalIntegrationBulkEditForm",
|
|
@@ -180,6 +181,7 @@ __all__ = (
|
|
|
180
181
|
"ObjectMetadataFilterForm",
|
|
181
182
|
"PasswordInputWithPlaceholder",
|
|
182
183
|
"RelationshipAssociationFilterForm",
|
|
184
|
+
"RelationshipBulkEditForm",
|
|
183
185
|
"RelationshipFilterForm",
|
|
184
186
|
"RelationshipForm",
|
|
185
187
|
"RoleBulkEditForm",
|
|
@@ -519,10 +521,11 @@ class CustomFieldBulkDeleteForm(ConfirmationForm):
|
|
|
519
521
|
else:
|
|
520
522
|
context = change_context.as_dict(queryset)
|
|
521
523
|
context["context_detail"] = "bulk delete custom field data"
|
|
522
|
-
tasks = [
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
tasks = []
|
|
525
|
+
for obj in queryset:
|
|
526
|
+
pk_set = set(obj.content_types.values_list("pk", flat=True))
|
|
527
|
+
if pk_set:
|
|
528
|
+
tasks.append(delete_custom_field_data.si(obj.key, pk_set, context))
|
|
526
529
|
return tasks
|
|
527
530
|
|
|
528
531
|
def perform_pre_delete(self, queryset):
|
|
@@ -533,10 +536,11 @@ class CustomFieldBulkDeleteForm(ConfirmationForm):
|
|
|
533
536
|
logger.error("Celery worker process not running. Object custom fields may fail to reflect this deletion.")
|
|
534
537
|
return
|
|
535
538
|
tasks = self.construct_custom_field_delete_tasks(queryset)
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
if tasks:
|
|
540
|
+
# Executing the tasks in the background sequentially using chain() aligns with how a single
|
|
541
|
+
# CustomField object is deleted. We decided to not check the result because it needs at least one worker
|
|
542
|
+
# to be active and comes with extra performance penalty.
|
|
543
|
+
chain(*tasks).apply_async()
|
|
540
544
|
|
|
541
545
|
|
|
542
546
|
#
|
|
@@ -749,6 +753,31 @@ class StaticGroupAssociationFilterForm(NautobotFilterForm):
|
|
|
749
753
|
#
|
|
750
754
|
# Export Templates
|
|
751
755
|
#
|
|
756
|
+
class ExportTemplateBulkEditForm(NautobotBulkEditForm):
|
|
757
|
+
pk = forms.ModelMultipleChoiceField(queryset=ExportTemplate.objects.all(), widget=forms.MultipleHiddenInput())
|
|
758
|
+
|
|
759
|
+
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
760
|
+
mime_type = forms.CharField(
|
|
761
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
762
|
+
required=False,
|
|
763
|
+
label="MIME type",
|
|
764
|
+
help_text="Defaults to <code>text/plain</code>",
|
|
765
|
+
)
|
|
766
|
+
file_extension = forms.CharField(
|
|
767
|
+
max_length=CHARFIELD_MAX_LENGTH, required=False, help_text="Extension to append to the rendered filename"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
content_type = forms.ModelChoiceField(
|
|
771
|
+
queryset=ContentType.objects.filter(FeatureQuery("export_templates").get_query()).order_by(
|
|
772
|
+
"app_label", "model"
|
|
773
|
+
),
|
|
774
|
+
required=False,
|
|
775
|
+
label="Content Type",
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
class Meta:
|
|
779
|
+
model = ExportTemplate
|
|
780
|
+
nullable_fields = ["description", "mime_type", "file_extension"]
|
|
752
781
|
|
|
753
782
|
|
|
754
783
|
class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
|
|
@@ -1774,6 +1803,45 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
|
|
|
1774
1803
|
#
|
|
1775
1804
|
|
|
1776
1805
|
|
|
1806
|
+
class RelationshipBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditFormMixin, NoteModelBulkEditFormMixin):
|
|
1807
|
+
pk = forms.ModelMultipleChoiceField(queryset=Relationship.objects.all(), widget=forms.MultipleHiddenInput())
|
|
1808
|
+
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
1809
|
+
type = forms.ChoiceField(
|
|
1810
|
+
required=False,
|
|
1811
|
+
label="type",
|
|
1812
|
+
choices=add_blank_choice(RelationshipTypeChoices),
|
|
1813
|
+
)
|
|
1814
|
+
source_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
|
|
1815
|
+
destination_hidden = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
|
|
1816
|
+
source_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the source")
|
|
1817
|
+
destination_filter = forms.JSONField(required=False, widget=forms.Textarea, help_text="Filter for the destination")
|
|
1818
|
+
source_type = CSVContentTypeField(
|
|
1819
|
+
queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()), required=False
|
|
1820
|
+
)
|
|
1821
|
+
destination_type = CSVContentTypeField(
|
|
1822
|
+
queryset=ContentType.objects.filter(FeatureQuery("relationships").get_query()), required=False
|
|
1823
|
+
)
|
|
1824
|
+
source_label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
1825
|
+
destination_label = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
1826
|
+
advanced_ui = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
|
|
1827
|
+
|
|
1828
|
+
class Meta:
|
|
1829
|
+
model = Relationship
|
|
1830
|
+
fields = [
|
|
1831
|
+
"description",
|
|
1832
|
+
"type",
|
|
1833
|
+
"source_hidden",
|
|
1834
|
+
"destination_hidden",
|
|
1835
|
+
"source_filter",
|
|
1836
|
+
"destination_filter",
|
|
1837
|
+
"source_type",
|
|
1838
|
+
"destination_type",
|
|
1839
|
+
"source_label",
|
|
1840
|
+
"destination_label",
|
|
1841
|
+
"advanced_ui",
|
|
1842
|
+
]
|
|
1843
|
+
|
|
1844
|
+
|
|
1777
1845
|
class RelationshipForm(BootstrapMixin, forms.ModelForm):
|
|
1778
1846
|
key = SlugField(
|
|
1779
1847
|
help_text="Internal name of this relationship. Please use underscores rather than dashes.",
|
|
@@ -822,16 +822,17 @@ class CustomField(
|
|
|
822
822
|
|
|
823
823
|
super().delete(*args, **kwargs)
|
|
824
824
|
|
|
825
|
-
|
|
826
|
-
|
|
825
|
+
if content_types:
|
|
826
|
+
# Circular Import
|
|
827
|
+
from nautobot.extras.signals import change_context_state
|
|
827
828
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
829
|
+
change_context = change_context_state.get()
|
|
830
|
+
if change_context is None:
|
|
831
|
+
context = None
|
|
832
|
+
else:
|
|
833
|
+
context = change_context.as_dict(instance=self)
|
|
834
|
+
context["context_detail"] = "delete custom field data"
|
|
835
|
+
delete_custom_field_data.delay(self.key, content_types, context)
|
|
835
836
|
|
|
836
837
|
def add_prefix_to_cf_key(self):
|
|
837
838
|
return "cf_" + str(self.key)
|
nautobot/extras/signals.py
CHANGED
|
@@ -125,6 +125,17 @@ def invalidate_relationship_models_cache(sender, **kwargs):
|
|
|
125
125
|
cache.delete_pattern(f"{method.cache_key_prefix}.*")
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
@receiver(post_save, sender=CustomField)
|
|
129
|
+
@receiver(post_delete, sender=CustomField)
|
|
130
|
+
@receiver(post_save, sender=Relationship)
|
|
131
|
+
@receiver(m2m_changed, sender=Relationship)
|
|
132
|
+
@receiver(post_delete, sender=Relationship)
|
|
133
|
+
def invalidate_openapi_schema_cache(sender, **kwargs):
|
|
134
|
+
"""Invalidate the openapi schema cache."""
|
|
135
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
136
|
+
cache.delete_pattern("openapi_schema_cache_*")
|
|
137
|
+
|
|
138
|
+
|
|
128
139
|
@receiver(post_save)
|
|
129
140
|
@receiver(m2m_changed)
|
|
130
141
|
def _handle_changed_object(sender, instance, raw=False, **kwargs):
|
|
@@ -327,7 +338,9 @@ def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs)
|
|
|
327
338
|
|
|
328
339
|
def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
|
|
329
340
|
"""
|
|
330
|
-
Handle
|
|
341
|
+
Handle provisioning/deprovisioning of custom_field_data when there are changes to CustomField.content_types.
|
|
342
|
+
|
|
343
|
+
The name of this function is misleading as this signal applies to *added* content-types as well.
|
|
331
344
|
"""
|
|
332
345
|
|
|
333
346
|
change_context = change_context_state.get()
|
|
@@ -335,13 +348,39 @@ def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
|
|
|
335
348
|
context = None
|
|
336
349
|
else:
|
|
337
350
|
context = change_context.as_dict(instance=instance)
|
|
338
|
-
|
|
339
|
-
|
|
351
|
+
|
|
352
|
+
if action == "pre_remove":
|
|
353
|
+
# Existing content types may be removed from the custom field, delete their data if so.
|
|
354
|
+
# CAUTION: pk_set in this _remove case is the content-types that were *requested* to remove,
|
|
355
|
+
# **not** the content-types that actually *will need to be* removed. In other words, this is not idempotent:
|
|
356
|
+
# my_cf.content_types.remove(device_ct) --> pk_set = {device_ct.pk}
|
|
357
|
+
# my_cf.content_types.remove(device_ct) --> pk_set = {device_ct.pk} again even though it was already gone
|
|
358
|
+
# So we need to check which content types will actually be removed to not create unnecessary tasks:
|
|
359
|
+
removed_pk_set = pk_set.intersection(instance.content_types.values_list("pk", flat=True))
|
|
360
|
+
if not removed_pk_set:
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
if context:
|
|
364
|
+
context["context_detail"] = "delete custom field data from existing content types"
|
|
365
|
+
transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, removed_pk_set, context))
|
|
366
|
+
|
|
367
|
+
elif action == "pre_clear":
|
|
368
|
+
# In this case, the provided pk_set is always empty, so we need to look at the current values instead:
|
|
369
|
+
cleared_pk_set = set(instance.content_types.values_list("pk", flat=True))
|
|
370
|
+
if not cleared_pk_set:
|
|
371
|
+
return
|
|
372
|
+
|
|
340
373
|
if context:
|
|
341
374
|
context["context_detail"] = "delete custom field data from existing content types"
|
|
342
|
-
transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key,
|
|
375
|
+
transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, cleared_pk_set, context))
|
|
343
376
|
|
|
344
377
|
elif action == "post_add":
|
|
378
|
+
# Unlike the above _remove case, in the _add case pk_set is the *new* content-types only,
|
|
379
|
+
# and for whatever reason, Django triggers this signal even if there was no actual change.
|
|
380
|
+
# To avoid creating unnecessary background tasks, we need to check for this case ourselves:
|
|
381
|
+
if not pk_set:
|
|
382
|
+
return
|
|
383
|
+
|
|
345
384
|
# New content types have been added to the custom field, provision them
|
|
346
385
|
if context:
|
|
347
386
|
context["context_detail"] = "provision custom field data for new content types"
|
nautobot/extras/tasks.py
CHANGED
|
@@ -121,12 +121,13 @@ def delete_custom_field_data(field_key, content_type_pk_set, change_context=None
|
|
|
121
121
|
for ct in ContentType.objects.filter(pk__in=content_type_pk_set):
|
|
122
122
|
model = ct.model_class()
|
|
123
123
|
queryset = model.objects.filter(**{f"_custom_field_data__{field_key}__isnull": False})
|
|
124
|
+
pk_list = []
|
|
124
125
|
if change_context is not None:
|
|
125
126
|
pk_list = list(queryset.values_list("pk", flat=True))
|
|
126
127
|
task_logger.info("Deleting existing values for custom field `%s` from %s records...", field_key, ct.model)
|
|
127
128
|
count = queryset.update(_custom_field_data=JSONRemove("_custom_field_data", field_key))
|
|
128
129
|
task_logger.info("Updated %d records", count)
|
|
129
|
-
if change_context is not None:
|
|
130
|
+
if count and change_context is not None:
|
|
130
131
|
# Since we used update() above, we bypassed ObjectChange automatic creation via signals. Create them now
|
|
131
132
|
_generate_bulk_object_changes(change_context, model.objects.filter(pk__in=pk_list), task_logger)
|
|
132
133
|
|
|
@@ -155,6 +156,7 @@ def provision_field(field_id, content_type_pk_set, change_context=None):
|
|
|
155
156
|
for ct in ContentType.objects.filter(pk__in=content_type_pk_set):
|
|
156
157
|
model = ct.model_class()
|
|
157
158
|
queryset = model.objects.filter(**{f"_custom_field_data__{field.key}__isnull": True})
|
|
159
|
+
pk_list = []
|
|
158
160
|
if change_context is not None:
|
|
159
161
|
pk_list = list(queryset.values_list("pk", flat=True))
|
|
160
162
|
task_logger.info(
|
|
@@ -165,7 +167,7 @@ def provision_field(field_id, content_type_pk_set, change_context=None):
|
|
|
165
167
|
)
|
|
166
168
|
count = queryset.update(_custom_field_data=JSONSet("_custom_field_data", field.key, field.default))
|
|
167
169
|
task_logger.info("Updated %d records.", count)
|
|
168
|
-
if change_context is not None:
|
|
170
|
+
if count and change_context is not None:
|
|
169
171
|
# Since we used update() above, we bypassed ObjectChange automatic creation via signals. Create them now
|
|
170
172
|
_generate_bulk_object_changes(change_context, model.objects.filter(pk__in=pk_list), task_logger)
|
|
171
173
|
|