nautobot 2.3.9__py3-none-any.whl → 2.3.11__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/utils.py +2 -0
- nautobot/cloud/tables.py +1 -0
- nautobot/core/forms/forms.py +5 -1
- nautobot/core/models/query_functions.py +147 -1
- nautobot/core/tables.py +88 -22
- nautobot/core/templates/generic/object_bulk_destroy.html +12 -3
- nautobot/core/templates/generic/object_bulk_update.html +4 -2
- nautobot/core/templates/generic/object_create.html +1 -1
- nautobot/core/templates/rest_framework/api.html +3 -0
- nautobot/core/testing/api.py +3 -1
- nautobot/core/testing/integration.py +64 -0
- nautobot/core/testing/views.py +33 -27
- nautobot/core/tests/integration/test_app_navbar.py +3 -3
- nautobot/core/tests/integration/test_navbar.py +1 -1
- nautobot/core/tests/test_csv.py +3 -0
- nautobot/core/tests/test_models_query_functions.py +108 -0
- nautobot/core/tests/test_utils.py +25 -5
- nautobot/core/utils/lookup.py +35 -0
- nautobot/core/views/generic.py +50 -39
- nautobot/core/views/mixins.py +97 -43
- nautobot/core/views/renderers.py +8 -5
- nautobot/dcim/tables/devices.py +3 -0
- nautobot/dcim/templates/dcim/device_component_add.html +8 -8
- nautobot/dcim/templates/dcim/modulebay_create.html +39 -0
- nautobot/dcim/templates/dcim/modulebay_update.html +39 -0
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +2 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +2 -2
- nautobot/dcim/tests/integration/test_create_device.py +86 -0
- nautobot/dcim/views.py +1 -1
- nautobot/extras/api/customfields.py +3 -10
- nautobot/extras/context_managers.py +23 -3
- nautobot/extras/jobs.py +20 -14
- nautobot/extras/models/customfields.py +12 -0
- nautobot/extras/signals.py +2 -0
- nautobot/extras/tasks.py +88 -69
- nautobot/extras/tests/test_context_managers.py +9 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/views.py +1 -0
- nautobot/extras/webhooks.py +16 -7
- nautobot/ipam/factory.py +3 -0
- nautobot/ipam/filters.py +5 -0
- nautobot/ipam/forms.py +17 -0
- nautobot/ipam/models.py +2 -1
- nautobot/ipam/signals.py +2 -2
- nautobot/ipam/tables.py +3 -3
- nautobot/ipam/templates/ipam/ipaddress_assign.html +2 -2
- nautobot/ipam/tests/test_models.py +113 -1
- nautobot/ipam/tests/test_views.py +39 -5
- nautobot/project-static/docs/404.html +1 -1
- nautobot/project-static/docs/apps/index.html +1 -1
- nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +62 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +132 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +176 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +95 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +5 -5
- nautobot/project-static/docs/development/apps/api/configuration-view.html +1 -1
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/global-search.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/graphql.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +1 -1
- nautobot/project-static/docs/development/apps/api/prometheus.html +1 -1
- nautobot/project-static/docs/development/apps/api/setup.html +1 -1
- nautobot/project-static/docs/development/apps/api/testing.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/base-template.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/notes.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/urls.html +1 -1
- nautobot/project-static/docs/development/apps/index.html +1 -1
- nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
- nautobot/project-static/docs/development/apps/migration/from-v1.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +1 -1
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +1 -1
- nautobot/project-static/docs/development/core/application-registry.html +1 -1
- nautobot/project-static/docs/development/core/best-practices.html +1 -1
- nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
- nautobot/project-static/docs/development/core/caching.html +1 -1
- nautobot/project-static/docs/development/core/controllers.html +1 -1
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +1 -1
- nautobot/project-static/docs/development/core/generic-views.html +1 -1
- nautobot/project-static/docs/development/core/getting-started.html +1 -1
- nautobot/project-static/docs/development/core/homepage.html +1 -1
- nautobot/project-static/docs/development/core/index.html +1 -1
- nautobot/project-static/docs/development/core/model-checklist.html +1 -1
- nautobot/project-static/docs/development/core/model-features.html +1 -1
- nautobot/project-static/docs/development/core/natural-keys.html +1 -1
- nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
- nautobot/project-static/docs/development/core/release-checklist.html +1 -1
- nautobot/project-static/docs/development/core/role-internals.html +1 -1
- nautobot/project-static/docs/development/core/settings.html +1 -1
- nautobot/project-static/docs/development/core/style-guide.html +1 -1
- nautobot/project-static/docs/development/core/templates.html +1 -1
- nautobot/project-static/docs/development/core/testing.html +1 -1
- nautobot/project-static/docs/development/core/user-preferences.html +1 -1
- nautobot/project-static/docs/development/index.html +1 -1
- nautobot/project-static/docs/development/jobs/index.html +1 -1
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
- nautobot/project-static/docs/index.html +1 -1
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +1 -1
- nautobot/project-static/docs/overview/design_philosophy.html +1 -1
- nautobot/project-static/docs/release-notes/index.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.0.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.1.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.2.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.4.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.5.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.6.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.0.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.1.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.3.html +440 -140
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +270 -270
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +40 -1
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -1
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +1 -1
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +1 -1
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +1 -1
- nautobot/project-static/docs/user-guide/index.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +1 -1
- nautobot/project-static/js/forms.js +0 -38
- nautobot/virtualization/forms.py +24 -0
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_views.py +7 -2
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/METADATA +2 -2
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/RECORD +335 -331
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/NOTICE +0 -0
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/WHEEL +0 -0
- {nautobot-2.3.9.dist-info → nautobot-2.3.11.dist-info}/entry_points.txt +0 -0
nautobot/core/views/mixins.py
CHANGED
|
@@ -414,7 +414,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
414
414
|
else:
|
|
415
415
|
# render the form with the error message.
|
|
416
416
|
data = {}
|
|
417
|
-
if self.action in ["bulk_update", "bulk_destroy"]:
|
|
417
|
+
if not request.POST.get("_all") and self.action in ["bulk_update", "bulk_destroy"]:
|
|
418
418
|
pk_list = self.pk_list
|
|
419
419
|
table_class = self.get_table_class()
|
|
420
420
|
table = table_class(queryset.filter(pk__in=pk_list), orderable=False)
|
|
@@ -578,6 +578,9 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
578
578
|
if not form_class:
|
|
579
579
|
if self.action == "bulk_destroy":
|
|
580
580
|
queryset = self.get_queryset()
|
|
581
|
+
bulk_delete_all = bool(self.request.POST.get("_all"))
|
|
582
|
+
if bulk_delete_all:
|
|
583
|
+
return ConfirmationForm
|
|
581
584
|
|
|
582
585
|
class BulkDestroyForm(ConfirmationForm):
|
|
583
586
|
pk = ModelMultipleChoiceField(queryset=queryset, widget=MultipleHiddenInput)
|
|
@@ -909,7 +912,48 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
|
|
|
909
912
|
return self.form_invalid(form)
|
|
910
913
|
|
|
911
914
|
|
|
912
|
-
class
|
|
915
|
+
class EditAndDeleteAllModelMixin:
|
|
916
|
+
"""
|
|
917
|
+
UI mixin to bulk destroy all and bulk edit all model instances.
|
|
918
|
+
"""
|
|
919
|
+
|
|
920
|
+
def _get_bulk_edit_delete_all_queryset(self, request):
|
|
921
|
+
"""
|
|
922
|
+
Retrieve the queryset of model instances to be bulk-deleted or bulk-deleted, filtered based on request parameters.
|
|
923
|
+
|
|
924
|
+
This method handles the retrieval of a queryset of model instances that match the specified
|
|
925
|
+
filter criteria in the request parameters, allowing a bulk delete operation to be performed
|
|
926
|
+
on all matching instances.
|
|
927
|
+
"""
|
|
928
|
+
model = self.queryset.model
|
|
929
|
+
|
|
930
|
+
# This Mixin is currently been used by both NautobotUIViewSet ObjectBulkDestroyViewMixin, ObjectBulkUpdateViewMixin
|
|
931
|
+
# BulkEditView, and BulkDeleteView which uses different keys for accessing filterset
|
|
932
|
+
filterset_class = getattr(self, "filterset", None)
|
|
933
|
+
if filterset_class is None:
|
|
934
|
+
filterset_class = getattr(self, "filterset_class", None)
|
|
935
|
+
|
|
936
|
+
if request.GET and filterset_class is not None:
|
|
937
|
+
queryset = filterset_class(request.GET, model.objects.all()).qs
|
|
938
|
+
# We take this approach because filterset.qs has already applied .distinct(),
|
|
939
|
+
# and performing a .delete directly on a queryset with .distinct applied is not allowed.
|
|
940
|
+
queryset = self.queryset.filter(pk__in=queryset)
|
|
941
|
+
else:
|
|
942
|
+
queryset = model.objects.all()
|
|
943
|
+
return queryset
|
|
944
|
+
|
|
945
|
+
def _bulk_delete_all_context(self, request, queryset):
|
|
946
|
+
model = queryset.model
|
|
947
|
+
return {
|
|
948
|
+
"obj_type_plural": model._meta.verbose_name_plural,
|
|
949
|
+
"return_url": self.get_return_url(request),
|
|
950
|
+
"total_objs_to_delete": queryset.count(),
|
|
951
|
+
"delete_all": True,
|
|
952
|
+
"table": None,
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, EditAndDeleteAllModelMixin):
|
|
913
957
|
"""
|
|
914
958
|
UI mixin to bulk destroy model instances.
|
|
915
959
|
"""
|
|
@@ -923,7 +967,10 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin):
|
|
|
923
967
|
queryset = self.get_queryset()
|
|
924
968
|
model = queryset.model
|
|
925
969
|
# Delete objects
|
|
926
|
-
|
|
970
|
+
if self.request.POST.get("_all"):
|
|
971
|
+
queryset = self._get_bulk_edit_delete_all_queryset(self.request)
|
|
972
|
+
else:
|
|
973
|
+
queryset = queryset.filter(pk__in=pk_list)
|
|
927
974
|
|
|
928
975
|
try:
|
|
929
976
|
with transaction.atomic():
|
|
@@ -951,35 +998,33 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin):
|
|
|
951
998
|
request.POST "_confirm": Function to validate the table form/BulkDestroyConfirmationForm and to perform the action of bulk destroy. Render the form with errors if exceptions are raised.
|
|
952
999
|
"""
|
|
953
1000
|
queryset = self.get_queryset()
|
|
954
|
-
|
|
1001
|
+
delete_all = bool(request.POST.get("_all"))
|
|
1002
|
+
data = {}
|
|
955
1003
|
# Are we deleting *all* objects in the queryset or just a selected subset?
|
|
956
|
-
if
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
self.filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True)
|
|
960
|
-
)
|
|
961
|
-
else:
|
|
962
|
-
self.pk_list = list(model.objects.all().values_list("pk", flat=True))
|
|
1004
|
+
if delete_all:
|
|
1005
|
+
queryset = self._get_bulk_edit_delete_all_queryset(self.request)
|
|
1006
|
+
data = self._bulk_delete_all_context(request, queryset)
|
|
963
1007
|
else:
|
|
964
1008
|
self.pk_list = list(request.POST.getlist("pk"))
|
|
1009
|
+
|
|
965
1010
|
form_class = self.get_form_class(**kwargs)
|
|
966
|
-
data = {}
|
|
967
1011
|
if "_confirm" in request.POST:
|
|
968
1012
|
form = form_class(request.POST, initial=normalize_querydict(request.GET, form_class=form_class))
|
|
969
1013
|
if form.is_valid():
|
|
970
1014
|
return self.form_valid(form)
|
|
971
1015
|
else:
|
|
972
1016
|
return self.form_invalid(form)
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1017
|
+
if not delete_all:
|
|
1018
|
+
table_class = self.get_table_class()
|
|
1019
|
+
table = table_class(queryset.filter(pk__in=self.pk_list), orderable=False)
|
|
1020
|
+
if not table.rows:
|
|
1021
|
+
messages.warning(
|
|
1022
|
+
request,
|
|
1023
|
+
f"No {queryset.model._meta.verbose_name_plural} were selected for deletion.",
|
|
1024
|
+
)
|
|
1025
|
+
return redirect(self.get_return_url(request))
|
|
981
1026
|
|
|
982
|
-
|
|
1027
|
+
data.update({"table": table})
|
|
983
1028
|
return Response(data)
|
|
984
1029
|
|
|
985
1030
|
|
|
@@ -1037,7 +1082,7 @@ class ObjectBulkCreateViewMixin(NautobotViewSetMixin): # 3.0 TODO: remove, unus
|
|
|
1037
1082
|
return self.form_invalid(form)
|
|
1038
1083
|
|
|
1039
1084
|
|
|
1040
|
-
class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
|
|
1085
|
+
class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, EditAndDeleteAllModelMixin):
|
|
1041
1086
|
"""
|
|
1042
1087
|
UI mixin to bulk update model instances.
|
|
1043
1088
|
"""
|
|
@@ -1062,7 +1107,13 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
|
|
|
1062
1107
|
nullified_fields = request.POST.getlist("_nullify")
|
|
1063
1108
|
with deferred_change_logging_for_bulk_operation():
|
|
1064
1109
|
updated_objects = []
|
|
1065
|
-
|
|
1110
|
+
edit_all = self.request.POST.get("_all")
|
|
1111
|
+
|
|
1112
|
+
if edit_all:
|
|
1113
|
+
queryset = self._get_bulk_edit_delete_all_queryset(self.request)
|
|
1114
|
+
else:
|
|
1115
|
+
queryset = queryset.filter(pk__in=form.cleaned_data["pk"])
|
|
1116
|
+
for obj in queryset:
|
|
1066
1117
|
self.obj = obj
|
|
1067
1118
|
# Update standard fields. If a field is listed in _nullify, delete its value.
|
|
1068
1119
|
for name in standard_fields:
|
|
@@ -1133,38 +1184,41 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin):
|
|
|
1133
1184
|
request.POST "_edit": Function to render the user selection of objects in a table form/BulkUpdateForm via Response that is passed to NautobotHTMLRenderer.
|
|
1134
1185
|
request.POST "_apply": Function to validate the table form/BulkUpdateForm and to perform the action of bulk update. Render the form with errors if exceptions are raised.
|
|
1135
1186
|
"""
|
|
1136
|
-
|
|
1137
|
-
model = queryset.model
|
|
1187
|
+
edit_all = request.POST.get("_all")
|
|
1138
1188
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
self.pk_list = list(
|
|
1143
|
-
self.filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True)
|
|
1144
|
-
)
|
|
1145
|
-
else:
|
|
1146
|
-
self.pk_list = list(model.objects.all().values_list("pk", flat=True))
|
|
1189
|
+
if edit_all:
|
|
1190
|
+
self.pk_list = None
|
|
1191
|
+
queryset = self._get_bulk_edit_delete_all_queryset(request)
|
|
1147
1192
|
else:
|
|
1148
1193
|
self.pk_list = list(request.POST.getlist("pk"))
|
|
1194
|
+
queryset = self.get_queryset().filter(pk__in=self.pk_list)
|
|
1195
|
+
|
|
1149
1196
|
data = {}
|
|
1150
1197
|
form_class = self.get_form_class()
|
|
1151
1198
|
if "_apply" in request.POST:
|
|
1152
1199
|
self.kwargs = kwargs
|
|
1153
|
-
form = form_class(queryset.model, request.POST)
|
|
1200
|
+
form = form_class(queryset.model, request.POST, edit_all=edit_all)
|
|
1154
1201
|
restrict_form_fields(form, request.user)
|
|
1155
1202
|
if form.is_valid():
|
|
1156
1203
|
return self.form_valid(form)
|
|
1157
1204
|
else:
|
|
1158
1205
|
return self.form_invalid(form)
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1206
|
+
table = None
|
|
1207
|
+
if not edit_all:
|
|
1208
|
+
table_class = self.get_table_class()
|
|
1209
|
+
table = table_class(queryset, orderable=False)
|
|
1210
|
+
if not table.rows:
|
|
1211
|
+
messages.warning(
|
|
1212
|
+
request,
|
|
1213
|
+
f"No {queryset.model._meta.verbose_name_plural} were selected to update.",
|
|
1214
|
+
)
|
|
1215
|
+
return redirect(self.get_return_url(request))
|
|
1216
|
+
data.update(
|
|
1217
|
+
{
|
|
1218
|
+
"table": table,
|
|
1219
|
+
"objs_count": queryset.count(),
|
|
1220
|
+
}
|
|
1221
|
+
)
|
|
1168
1222
|
return Response(data)
|
|
1169
1223
|
|
|
1170
1224
|
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -249,19 +249,22 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
249
249
|
"return_url": return_url,
|
|
250
250
|
}
|
|
251
251
|
form = form_class(initial=initial)
|
|
252
|
-
|
|
252
|
+
delete_all = request.POST.get("_all")
|
|
253
|
+
if not delete_all:
|
|
254
|
+
table = self.construct_table(view, pk_list=pk_list)
|
|
253
255
|
elif view.action == "bulk_create": # 3.0 TODO: remove, replaced by ImportObjects system Job
|
|
254
256
|
form = view.get_form()
|
|
255
257
|
if request.data:
|
|
256
258
|
table = data.get("table")
|
|
257
259
|
elif view.action == "bulk_update":
|
|
260
|
+
edit_all = request.POST.get("_all")
|
|
258
261
|
pk_list = getattr(view, "pk_list", [])
|
|
259
|
-
if pk_list:
|
|
262
|
+
if pk_list or edit_all:
|
|
260
263
|
initial_data = {"pk": pk_list}
|
|
261
|
-
form = form_class(model, initial=initial_data)
|
|
262
|
-
|
|
264
|
+
form = form_class(model, initial=initial_data, edit_all=edit_all)
|
|
263
265
|
restrict_form_fields(form, request.user)
|
|
264
|
-
|
|
266
|
+
if not edit_all:
|
|
267
|
+
table = self.construct_table(view, pk_list=pk_list)
|
|
265
268
|
elif view.action == "notes":
|
|
266
269
|
initial_data = {
|
|
267
270
|
"assigned_object_type": content_type,
|
nautobot/dcim/tables/devices.py
CHANGED
|
@@ -1277,6 +1277,9 @@ class SoftwareImageFileTable(StatusTableMixin, BaseTable):
|
|
|
1277
1277
|
class SoftwareVersionTable(StatusTableMixin, BaseTable):
|
|
1278
1278
|
pk = ToggleColumn()
|
|
1279
1279
|
version = tables.Column(linkify=True)
|
|
1280
|
+
platform = tables.Column(linkify=True)
|
|
1281
|
+
release_date = tables.DateColumn()
|
|
1282
|
+
end_of_support_date = tables.DateColumn()
|
|
1280
1283
|
software_image_file_count = LinkedCountColumn(
|
|
1281
1284
|
viewname="dcim:softwareimagefile_list",
|
|
1282
1285
|
url_params={"software_version": "pk"},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<form action="" method="post" class="form form-horizontal">
|
|
8
8
|
{% csrf_token %}
|
|
9
9
|
<div class="row">
|
|
10
|
-
<div class="col-md-
|
|
10
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
11
11
|
{% if form.non_field_errors %}
|
|
12
12
|
<div class="panel panel-danger">
|
|
13
13
|
<div class="panel-heading"><strong>Errors</strong></div>
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
{% include 'inc/extras_features_edit_form_fields.html' with form=model_form %}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
</
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="row">
|
|
31
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 text-right">
|
|
32
|
+
<button type="submit" name="_create" class="btn btn-primary">Create</button>
|
|
33
|
+
<button type="submit" name="_addanother" class="btn btn-primary">Create and Add More</button>
|
|
34
|
+
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
</form>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% extends 'dcim/device_component_add.html' %}
|
|
2
|
+
|
|
3
|
+
{% block javascript %}
|
|
4
|
+
{{ block.super }}
|
|
5
|
+
<script>
|
|
6
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
7
|
+
var position_field = document.getElementById('id_position_pattern');
|
|
8
|
+
var source_arr = position_field.getAttribute('source').split(" ");
|
|
9
|
+
var length = position_field.getAttribute('maxlength');
|
|
10
|
+
position_field.setAttribute('_changed', Boolean(position_field.value))
|
|
11
|
+
position_field.addEventListener('change', function() {
|
|
12
|
+
position_field.setAttribute('_changed', Boolean(position_field.value))
|
|
13
|
+
});
|
|
14
|
+
function repopulate() {
|
|
15
|
+
let str = "";
|
|
16
|
+
for (source_str of source_arr) {
|
|
17
|
+
if (str != "") {
|
|
18
|
+
str += " ";
|
|
19
|
+
}
|
|
20
|
+
let source_id = 'id_' + source_str;
|
|
21
|
+
let source = document.getElementById(source_id)
|
|
22
|
+
str += source.value;
|
|
23
|
+
}
|
|
24
|
+
position_field.value = str.slice(0, length ? length : 255);
|
|
25
|
+
};
|
|
26
|
+
for (source_str of source_arr) {
|
|
27
|
+
let source_id = 'id_' + source_str;
|
|
28
|
+
let source = document.getElementById(source_id);
|
|
29
|
+
source.addEventListener('keyup', function() {
|
|
30
|
+
if (position_field && position_field.getAttribute('_changed')=="false") {
|
|
31
|
+
repopulate();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
document.getElementsByClassName('reslugify')[0].addEventListener('click', repopulate);
|
|
36
|
+
document.getElementsByClassName('reslugify')[0].setAttribute('data-original-title', "Regenerate position");
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
{% endblock javascript %}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% extends 'generic/object_create.html' %}
|
|
2
|
+
|
|
3
|
+
{% block javascript %}
|
|
4
|
+
{{ block.super }}
|
|
5
|
+
<script>
|
|
6
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
7
|
+
var position_field = document.getElementById('id_position');
|
|
8
|
+
var source_arr = position_field.getAttribute('source').split(" ");
|
|
9
|
+
var length = position_field.getAttribute('maxlength');
|
|
10
|
+
position_field.setAttribute('_changed', Boolean(position_field.value))
|
|
11
|
+
position_field.addEventListener('change', function() {
|
|
12
|
+
position_field.setAttribute('_changed', Boolean(position_field.value))
|
|
13
|
+
});
|
|
14
|
+
function repopulate() {
|
|
15
|
+
let str = "";
|
|
16
|
+
for (source_str of source_arr) {
|
|
17
|
+
if (str != "") {
|
|
18
|
+
str += " ";
|
|
19
|
+
}
|
|
20
|
+
let source_id = 'id_' + source_str;
|
|
21
|
+
let source = document.getElementById(source_id)
|
|
22
|
+
str += source.value;
|
|
23
|
+
}
|
|
24
|
+
position_field.value = str.slice(0, length ? length : 255);
|
|
25
|
+
};
|
|
26
|
+
for (source_str of source_arr) {
|
|
27
|
+
let source_id = 'id_' + source_str;
|
|
28
|
+
let source = document.getElementById(source_id);
|
|
29
|
+
source.addEventListener('keyup', function() {
|
|
30
|
+
if (position_field && position_field.getAttribute('_changed')=="false") {
|
|
31
|
+
repopulate();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
document.getElementsByClassName('reslugify')[0].addEventListener('click', repopulate);
|
|
36
|
+
document.getElementsByClassName('reslugify')[0].setAttribute('data-original-title', "Regenerate position");
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
{% endblock javascript %}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<form action="" method="post" enctype="multipart/form-data" class="form form-horizontal">
|
|
6
6
|
{% csrf_token %}
|
|
7
7
|
<div class="row">
|
|
8
|
-
<div class="col-md-
|
|
8
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
9
9
|
<h3>{% block title %}Add New Member to Virtual Chassis {{ virtual_chassis }}{% endblock %}</h3>
|
|
10
10
|
{% if membership_form.non_field_errors %}
|
|
11
11
|
<div class="panel panel-danger">
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
<div class="row">
|
|
28
|
-
<div class="col-md-
|
|
28
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 text-right">
|
|
29
29
|
<button type="submit" name="_save" class="btn btn-primary">Save</button>
|
|
30
30
|
<button type="submit" name="_addanother" class="btn btn-primary">Add Another</button>
|
|
31
31
|
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
{{ pk_form.pk }}
|
|
9
9
|
{{ formset.management_form }}
|
|
10
10
|
<div class="row">
|
|
11
|
-
<div class="col-
|
|
11
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
12
12
|
<h3>{% block title %}{% if vc_form.instance %}Editing {{ vc_form.instance }}{% else %}New Virtual Chassis{% endif %}{% endblock %}</h3>
|
|
13
13
|
{% if vc_form.non_field_errors %}
|
|
14
14
|
<div class="panel panel-danger">
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
</div>
|
|
85
85
|
</div>
|
|
86
86
|
<div class="row">
|
|
87
|
-
<div class="col-
|
|
87
|
+
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 text-right">
|
|
88
88
|
<button type="submit" name="_update" class="btn btn-primary">Update</button>
|
|
89
89
|
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
|
|
90
90
|
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from django.urls import reverse
|
|
2
|
+
|
|
3
|
+
from nautobot.core.testing.integration import SeleniumTestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreateDeviceTestCase(SeleniumTestCase):
|
|
7
|
+
"""
|
|
8
|
+
Create a device and all pre-requisite objects through the UI.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def test_create_device(self):
|
|
12
|
+
"""
|
|
13
|
+
This test goes through the process of creating a device in the UI. All pre-requisite objects are created:
|
|
14
|
+
- Manufacturer
|
|
15
|
+
- Device Type
|
|
16
|
+
- LocationType
|
|
17
|
+
- Location
|
|
18
|
+
- Role
|
|
19
|
+
- Device
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
self.user.is_superuser = True
|
|
23
|
+
self.user.save()
|
|
24
|
+
self.login(self.user.username, self.password)
|
|
25
|
+
|
|
26
|
+
# Manufacturer
|
|
27
|
+
self.click_navbar_entry("Devices", "Manufacturers")
|
|
28
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:manufacturer_list"))
|
|
29
|
+
self.click_list_view_add_button()
|
|
30
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:manufacturer_add"))
|
|
31
|
+
self.browser.fill("name", "Test Manufacturer 1")
|
|
32
|
+
self.click_edit_form_create_button()
|
|
33
|
+
|
|
34
|
+
# Device Type
|
|
35
|
+
self.click_navbar_entry("Devices", "Device Types")
|
|
36
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:devicetype_list"))
|
|
37
|
+
self.click_list_view_add_button()
|
|
38
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:devicetype_add"))
|
|
39
|
+
self.fill_select2_field("manufacturer", "Test Manufacturer 1")
|
|
40
|
+
self.browser.fill("model", "Test Device Type 1")
|
|
41
|
+
self.click_edit_form_create_button()
|
|
42
|
+
|
|
43
|
+
# LocationType
|
|
44
|
+
self.click_navbar_entry("Organization", "Location Types")
|
|
45
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:locationtype_list"))
|
|
46
|
+
self.click_list_view_add_button()
|
|
47
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:locationtype_add"))
|
|
48
|
+
self.fill_select2_multiselect_field("content_types", "dcim | device")
|
|
49
|
+
self.browser.fill("name", "Test Location Type 1")
|
|
50
|
+
self.click_edit_form_create_button()
|
|
51
|
+
|
|
52
|
+
# Location
|
|
53
|
+
self.click_navbar_entry("Organization", "Locations")
|
|
54
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_list"))
|
|
55
|
+
self.click_list_view_add_button()
|
|
56
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:location_add"))
|
|
57
|
+
self.fill_select2_field("location_type", "Test Location Type 1")
|
|
58
|
+
self.fill_select2_field("status", "") # pick first status
|
|
59
|
+
self.browser.fill("name", "Test Location 1")
|
|
60
|
+
self.click_edit_form_create_button()
|
|
61
|
+
|
|
62
|
+
# Role
|
|
63
|
+
self.click_navbar_entry("Organization", "Roles")
|
|
64
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("extras:role_list"))
|
|
65
|
+
self.click_list_view_add_button()
|
|
66
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("extras:role_add"))
|
|
67
|
+
self.browser.fill("name", "Test Role 1")
|
|
68
|
+
self.fill_select2_multiselect_field("content_types", "dcim | device")
|
|
69
|
+
self.click_edit_form_create_button()
|
|
70
|
+
|
|
71
|
+
# Device
|
|
72
|
+
self.click_navbar_entry("Devices", "Devices")
|
|
73
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_list"))
|
|
74
|
+
self.click_list_view_add_button()
|
|
75
|
+
self.assertEqual(self.browser.url, self.live_server_url + reverse("dcim:device_add"))
|
|
76
|
+
self.browser.fill("name", "Test Device Integration Test 1")
|
|
77
|
+
self.fill_select2_field("role", "Test Role 1")
|
|
78
|
+
self.fill_select2_field("device_type", "Test Device Type 1")
|
|
79
|
+
self.fill_select2_field("location", "Test Location 1")
|
|
80
|
+
self.fill_select2_field("status", "") # pick first status
|
|
81
|
+
self.click_edit_form_create_button()
|
|
82
|
+
|
|
83
|
+
# Assert that the device was created
|
|
84
|
+
self.assertTrue(self.browser.is_text_present("Created device Test Device Integration Test 1", wait_time=5))
|
|
85
|
+
self.assertTrue(self.browser.is_text_present("Test Location 1", wait_time=5))
|
|
86
|
+
self.assertTrue(self.browser.is_text_present("Test Device Type 1", wait_time=5))
|
nautobot/dcim/views.py
CHANGED
|
@@ -3241,7 +3241,7 @@ class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
|
|
|
3241
3241
|
model_form_class = forms.ModuleBayForm
|
|
3242
3242
|
serializer_class = serializers.ModuleBaySerializer
|
|
3243
3243
|
table_class = tables.ModuleBayTable
|
|
3244
|
-
create_template_name = "dcim/
|
|
3244
|
+
create_template_name = "dcim/modulebay_create.html"
|
|
3245
3245
|
|
|
3246
3246
|
def get_extra_context(self, request, instance):
|
|
3247
3247
|
if instance:
|
|
@@ -40,15 +40,7 @@ class CustomFieldDefaultValues:
|
|
|
40
40
|
class CustomFieldsDataField(Field):
|
|
41
41
|
@property
|
|
42
42
|
def custom_field_keys(self):
|
|
43
|
-
|
|
44
|
-
Cache CustomField keys assigned to this model to avoid redundant database queries
|
|
45
|
-
"""
|
|
46
|
-
if not hasattr(self, "_custom_field_keys"):
|
|
47
|
-
content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
|
|
48
|
-
self._custom_field_keys = CustomField.objects.filter(content_types=content_type).values_list(
|
|
49
|
-
"key", flat=True
|
|
50
|
-
)
|
|
51
|
-
return self._custom_field_keys
|
|
43
|
+
return CustomField.objects.keys_for_model(self.parent.Meta.model)
|
|
52
44
|
|
|
53
45
|
def to_representation(self, obj):
|
|
54
46
|
return {key: obj.get(key) for key in self.custom_field_keys}
|
|
@@ -58,7 +50,8 @@ class CustomFieldsDataField(Field):
|
|
|
58
50
|
|
|
59
51
|
# Discard any entries in data that do not align with actual CustomFields - this matches the REST API behavior
|
|
60
52
|
# for top-level serializer fields that do not exist or are not writable
|
|
61
|
-
|
|
53
|
+
custom_field_keys = self.custom_field_keys
|
|
54
|
+
data = {key: value for key, value in data.items() if key in custom_field_keys}
|
|
62
55
|
|
|
63
56
|
# If updating an existing instance, start with existing _custom_field_data
|
|
64
57
|
if self.parent.instance:
|
|
@@ -204,14 +204,34 @@ def web_request_context(
|
|
|
204
204
|
yield request
|
|
205
205
|
finally:
|
|
206
206
|
jobs_reloaded = False
|
|
207
|
+
# In bulk operations, we are performing the same action (create/update/delete) on the same content-type.
|
|
208
|
+
# Save some repeated database queries by reusing the same evaluated querysets where applicable:
|
|
209
|
+
jobhook_queryset = None
|
|
210
|
+
webhook_queryset = None
|
|
211
|
+
last_action = None
|
|
212
|
+
last_content_type = None
|
|
207
213
|
# enqueue jobhooks and webhooks, use change_context.change_id in case change_id was not supplied
|
|
208
214
|
for object_change in (
|
|
209
|
-
ObjectChange.objects.
|
|
215
|
+
ObjectChange.objects.select_related("changed_object_type", "user")
|
|
216
|
+
.filter(request_id=change_context.change_id)
|
|
217
|
+
.order_by("time") # default ordering is -time but we want oldest first not newest first
|
|
218
|
+
.iterator()
|
|
210
219
|
):
|
|
220
|
+
if object_change.action != last_action or object_change.changed_object_type != last_content_type:
|
|
221
|
+
jobhook_queryset = None
|
|
222
|
+
webhook_queryset = None
|
|
223
|
+
|
|
211
224
|
if context != ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK:
|
|
212
225
|
# Make sure JobHooks are up to date (only once) before calling them
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
did_reload_jobs, jobhook_queryset = enqueue_job_hooks(
|
|
227
|
+
object_change, may_reload_jobs=(not jobs_reloaded), jobhook_queryset=jobhook_queryset
|
|
228
|
+
)
|
|
229
|
+
if did_reload_jobs:
|
|
230
|
+
jobs_reloaded = True
|
|
231
|
+
|
|
232
|
+
webhook_queryset = enqueue_webhooks(object_change, webhook_queryset=webhook_queryset)
|
|
233
|
+
last_action = object_change.action
|
|
234
|
+
last_content_type = object_change.changed_object_type
|
|
215
235
|
|
|
216
236
|
|
|
217
237
|
@contextmanager
|
nautobot/extras/jobs.py
CHANGED
|
@@ -1144,41 +1144,47 @@ def run_job(self, job_class_path, *args, **kwargs):
|
|
|
1144
1144
|
raise
|
|
1145
1145
|
|
|
1146
1146
|
|
|
1147
|
-
def enqueue_job_hooks(object_change, may_reload_jobs=True):
|
|
1147
|
+
def enqueue_job_hooks(object_change, may_reload_jobs=True, jobhook_queryset=None):
|
|
1148
1148
|
"""
|
|
1149
1149
|
Find job hook(s) assigned to this changed object type + action and enqueue them to be processed.
|
|
1150
1150
|
|
|
1151
|
+
Args:
|
|
1152
|
+
object_change (ObjectChange): The change that may trigger JobHooks to execute.
|
|
1153
|
+
may_reload_jobs (bool): Whether to reload JobHook source code from disk to guarantee up-to-date code.
|
|
1154
|
+
jobhook_queryset (QuerySet): Previously retrieved set of JobHooks to potentially enqueue
|
|
1155
|
+
|
|
1151
1156
|
Returns:
|
|
1152
|
-
|
|
1157
|
+
result (tuple[bool, QuerySet]): whether Jobs were reloaded here, and the jobhooks that were considered
|
|
1153
1158
|
"""
|
|
1154
1159
|
jobs_reloaded = False
|
|
1155
1160
|
|
|
1156
1161
|
# Job hooks cannot trigger other job hooks
|
|
1157
1162
|
if object_change.change_context == ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK:
|
|
1158
|
-
return jobs_reloaded
|
|
1163
|
+
return jobs_reloaded, jobhook_queryset
|
|
1159
1164
|
|
|
1160
1165
|
# Determine whether this type of object supports job hooks
|
|
1161
1166
|
content_type = object_change.changed_object_type
|
|
1162
1167
|
if content_type not in change_logged_models_queryset():
|
|
1163
|
-
return jobs_reloaded
|
|
1168
|
+
return jobs_reloaded, jobhook_queryset
|
|
1164
1169
|
|
|
1165
1170
|
# Retrieve any applicable job hooks
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1171
|
+
if jobhook_queryset is None:
|
|
1172
|
+
action_flag = {
|
|
1173
|
+
ObjectChangeActionChoices.ACTION_CREATE: "type_create",
|
|
1174
|
+
ObjectChangeActionChoices.ACTION_UPDATE: "type_update",
|
|
1175
|
+
ObjectChangeActionChoices.ACTION_DELETE: "type_delete",
|
|
1176
|
+
}[object_change.action]
|
|
1177
|
+
jobhook_queryset = JobHook.objects.filter(content_types=content_type, enabled=True, **{action_flag: True})
|
|
1172
1178
|
|
|
1173
|
-
if not
|
|
1174
|
-
return jobs_reloaded
|
|
1179
|
+
if not jobhook_queryset: # not .exists() as we *want* to populate the queryset cache
|
|
1180
|
+
return jobs_reloaded, jobhook_queryset
|
|
1175
1181
|
|
|
1176
1182
|
# Enqueue the jobs related to the job_hooks
|
|
1177
1183
|
if may_reload_jobs:
|
|
1178
1184
|
get_jobs(reload=True)
|
|
1179
1185
|
jobs_reloaded = True
|
|
1180
1186
|
|
|
1181
|
-
for job_hook in
|
|
1187
|
+
for job_hook in jobhook_queryset:
|
|
1182
1188
|
job_model = job_hook.job
|
|
1183
1189
|
if not job_model.installed or not job_model.enabled:
|
|
1184
1190
|
logger.warning(
|
|
@@ -1189,4 +1195,4 @@ def enqueue_job_hooks(object_change, may_reload_jobs=True):
|
|
|
1189
1195
|
else:
|
|
1190
1196
|
JobResult.enqueue_job(job_model, object_change.user, object_change=object_change.pk)
|
|
1191
1197
|
|
|
1192
|
-
return jobs_reloaded
|
|
1198
|
+
return jobs_reloaded, jobhook_queryset
|