nautobot 2.2.4__py3-none-any.whl → 2.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/circuits/forms.py +15 -0
- nautobot/circuits/navigation.py +9 -1
- nautobot/circuits/views.py +2 -0
- nautobot/core/filters.py +11 -0
- nautobot/core/settings.yaml +3 -3
- nautobot/core/testing/filters.py +24 -1
- nautobot/core/testing/views.py +13 -1
- nautobot/core/views/utils.py +18 -1
- nautobot/dcim/filters/__init__.py +1 -1
- nautobot/dcim/forms.py +23 -4
- nautobot/dcim/tables/devicetypes.py +15 -4
- nautobot/dcim/tests/test_views.py +84 -0
- nautobot/dcim/views.py +3 -0
- nautobot/extras/api/views.py +2 -2
- nautobot/extras/context_managers.py +3 -0
- nautobot/extras/filters/__init__.py +15 -1
- nautobot/extras/forms/forms.py +33 -0
- nautobot/extras/forms/mixins.py +0 -6
- nautobot/extras/signals.py +6 -1
- nautobot/extras/tests/test_api.py +24 -2
- nautobot/extras/tests/test_context_managers.py +33 -1
- nautobot/extras/tests/test_filters.py +69 -0
- nautobot/extras/tests/test_forms.py +0 -3
- nautobot/extras/tests/test_views.py +48 -4
- nautobot/extras/views.py +12 -10
- nautobot/ipam/forms.py +18 -0
- nautobot/ipam/tests/test_views.py +9 -2
- nautobot/ipam/views.py +11 -0
- nautobot/project-static/docs/404.html +3 -3
- nautobot/project-static/docs/apps/index.html +3 -3
- nautobot/project-static/docs/apps/nautobot-apps.html +3 -3
- nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js → bundle.ebd0bdb7.min.js} +5 -5
- nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js.map → bundle.ebd0bdb7.min.js.map} +3 -3
- nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css → main.6543a935.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css.map → main.6543a935.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +3 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +3 -3
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/global-search.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +3 -3
- nautobot/project-static/docs/development/apps/api/prometheus.html +3 -3
- nautobot/project-static/docs/development/apps/api/setup.html +3 -3
- nautobot/project-static/docs/development/apps/api/testing.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/notes.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/urls.html +3 -3
- nautobot/project-static/docs/development/apps/index.html +3 -3
- nautobot/project-static/docs/development/apps/migration/code-updates.html +3 -3
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +3 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +5 -5
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +3 -3
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +3 -3
- nautobot/project-static/docs/development/core/application-registry.html +4 -4
- nautobot/project-static/docs/development/core/best-practices.html +3 -3
- nautobot/project-static/docs/development/core/bootstrap-ui.html +3 -3
- nautobot/project-static/docs/development/core/caching.html +3 -3
- nautobot/project-static/docs/development/core/controllers.html +3 -3
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +3 -3
- nautobot/project-static/docs/development/core/generic-views.html +3 -3
- nautobot/project-static/docs/development/core/getting-started.html +5 -5
- nautobot/project-static/docs/development/core/homepage.html +3 -3
- nautobot/project-static/docs/development/core/index.html +3 -3
- nautobot/project-static/docs/development/core/model-checklist.html +3 -3
- nautobot/project-static/docs/development/core/model-features.html +3 -3
- nautobot/project-static/docs/development/core/natural-keys.html +3 -3
- nautobot/project-static/docs/development/core/navigation-menu.html +3 -3
- nautobot/project-static/docs/development/core/release-checklist.html +3 -3
- nautobot/project-static/docs/development/core/role-internals.html +3 -3
- nautobot/project-static/docs/development/core/settings.html +3 -3
- nautobot/project-static/docs/development/core/style-guide.html +3 -3
- nautobot/project-static/docs/development/core/templates.html +3 -3
- nautobot/project-static/docs/development/core/testing.html +4 -4
- nautobot/project-static/docs/development/core/user-preferences.html +3 -3
- nautobot/project-static/docs/development/index.html +3 -3
- nautobot/project-static/docs/development/jobs/index.html +3 -3
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -3
- nautobot/project-static/docs/index.html +3 -3
- nautobot/project-static/docs/release-notes/index.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.0.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.1.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.2.html +5 -5
- nautobot/project-static/docs/release-notes/version-1.3.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.4.html +5 -5
- nautobot/project-static/docs/release-notes/version-1.5.html +5 -5
- nautobot/project-static/docs/release-notes/version-1.6.html +543 -163
- nautobot/project-static/docs/release-notes/version-2.0.html +6 -6
- nautobot/project-static/docs/release-notes/version-2.1.html +3 -3
- nautobot/project-static/docs/release-notes/version-2.2.html +263 -84
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +254 -254
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +5 -5
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +6 -6
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +7 -7
- nautobot/project-static/docs/user-guide/administration/installation/services.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +3 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +3 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +3 -22
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +5 -5
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +5 -5
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +6 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +3 -3
- nautobot/project-static/docs/user-guide/index.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +5 -5
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +5 -5
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +4 -36
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +3 -3
- nautobot/project-static/js/forms.js +2 -1
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/views.py +1 -0
- nautobot/virtualization/forms.py +18 -6
- nautobot/virtualization/templates/virtualization/clustertype.html +2 -2
- nautobot/virtualization/views.py +2 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/METADATA +1 -1
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/RECORD +303 -303
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/NOTICE +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/WHEEL +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.5.dist-info}/entry_points.txt +0 -0
nautobot/circuits/forms.py
CHANGED
|
@@ -145,6 +145,12 @@ class CircuitTypeForm(NautobotModelForm):
|
|
|
145
145
|
]
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
class CircuitTypeFilterForm(NautobotFilterForm):
|
|
149
|
+
model = CircuitType
|
|
150
|
+
q = forms.CharField(required=False, label="Search")
|
|
151
|
+
name = forms.CharField(required=False)
|
|
152
|
+
|
|
153
|
+
|
|
148
154
|
#
|
|
149
155
|
# Circuits
|
|
150
156
|
#
|
|
@@ -262,3 +268,12 @@ class CircuitTerminationForm(LocatableModelFormMixin, NautobotModelForm):
|
|
|
262
268
|
widgets = {
|
|
263
269
|
"term_side": forms.HiddenInput(),
|
|
264
270
|
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class CircuitTerminationFilterForm(LocatableModelFilterFormMixin, NautobotFilterForm):
|
|
274
|
+
model = CircuitTermination
|
|
275
|
+
q = forms.CharField(required=False, label="Search")
|
|
276
|
+
circuit = DynamicModelMultipleChoiceField(queryset=Circuit.objects.all(), to_field_name="cid", required=False)
|
|
277
|
+
provider_network = DynamicModelMultipleChoiceField(
|
|
278
|
+
queryset=ProviderNetwork.objects.all(), to_field_name="name", required=False
|
|
279
|
+
)
|
nautobot/circuits/navigation.py
CHANGED
|
@@ -33,10 +33,18 @@ menu_items = (
|
|
|
33
33
|
),
|
|
34
34
|
),
|
|
35
35
|
),
|
|
36
|
+
NavMenuItem(
|
|
37
|
+
link="circuits:circuittermination_list",
|
|
38
|
+
name="Circuit Terminations",
|
|
39
|
+
weight=200,
|
|
40
|
+
permissions=[
|
|
41
|
+
"circuits.view_circuittermination",
|
|
42
|
+
],
|
|
43
|
+
),
|
|
36
44
|
NavMenuItem(
|
|
37
45
|
link="circuits:circuittype_list",
|
|
38
46
|
name="Circuit Types",
|
|
39
|
-
weight=
|
|
47
|
+
weight=300,
|
|
40
48
|
permissions=[
|
|
41
49
|
"circuits.view_circuittype",
|
|
42
50
|
],
|
nautobot/circuits/views.py
CHANGED
|
@@ -27,6 +27,7 @@ class CircuitTypeUIViewSet(
|
|
|
27
27
|
view_mixins.ObjectNotesViewMixin,
|
|
28
28
|
):
|
|
29
29
|
filterset_class = filters.CircuitTypeFilterSet
|
|
30
|
+
filterset_form_class = forms.CircuitTypeFilterForm
|
|
30
31
|
form_class = forms.CircuitTypeForm
|
|
31
32
|
queryset = CircuitType.objects.annotate(circuit_count=count_related(Circuit, "circuit_type"))
|
|
32
33
|
serializer_class = serializers.CircuitTypeSerializer
|
|
@@ -67,6 +68,7 @@ class CircuitTerminationUIViewSet(
|
|
|
67
68
|
):
|
|
68
69
|
action_buttons = ("import", "export")
|
|
69
70
|
filterset_class = filters.CircuitTerminationFilterSet
|
|
71
|
+
filterset_form_class = forms.CircuitTerminationFilterForm
|
|
70
72
|
form_class = forms.CircuitTerminationForm
|
|
71
73
|
queryset = CircuitTermination.objects.all()
|
|
72
74
|
serializer_class = serializers.CircuitTerminationSerializer
|
nautobot/core/filters.py
CHANGED
|
@@ -176,6 +176,9 @@ class ContentTypeFilterMixin:
|
|
|
176
176
|
if value in EMPTY_VALUES:
|
|
177
177
|
return qs
|
|
178
178
|
|
|
179
|
+
if value.isdigit():
|
|
180
|
+
return qs.filter(**{f"{self.field_name}__pk": value})
|
|
181
|
+
|
|
179
182
|
try:
|
|
180
183
|
app_label, model = value.lower().split(".")
|
|
181
184
|
except ValueError:
|
|
@@ -242,6 +245,9 @@ class ContentTypeMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
|
|
|
242
245
|
if self.conjoined:
|
|
243
246
|
qs = ContentTypeFilter.filter(self, qs, v)
|
|
244
247
|
else:
|
|
248
|
+
if v.isdigit():
|
|
249
|
+
q |= models.Q(**{f"{self.field_name}__pk": value})
|
|
250
|
+
continue
|
|
245
251
|
# Similar to the ContentTypeFilter.filter() call above, but instead of narrowing the query each time
|
|
246
252
|
# (a AND b AND c ...) we broaden the query each time (a OR b OR c ...).
|
|
247
253
|
# Specifically, we're mapping a value like ['dcim.device', 'ipam.vlan'] to a query like
|
|
@@ -716,6 +722,11 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
716
722
|
"""
|
|
717
723
|
filters = super().get_filters()
|
|
718
724
|
|
|
725
|
+
# Remove any filters that may have been auto-generated from private model attributes
|
|
726
|
+
for filter_name in list(filters.keys()):
|
|
727
|
+
if filter_name.startswith("_"):
|
|
728
|
+
del filters[filter_name]
|
|
729
|
+
|
|
719
730
|
# django-filters has no concept of "abstract" filtersets, so we have to fake it
|
|
720
731
|
if cls._meta.model is not None:
|
|
721
732
|
new_filters = {}
|
nautobot/core/settings.yaml
CHANGED
|
@@ -773,7 +773,7 @@ properties:
|
|
|
773
773
|
description: "A mapping of permissions to assign a new user account when created using SSO authentication."
|
|
774
774
|
details: |-
|
|
775
775
|
Each key in the dictionary will be the permission name specified as `<app_label>.<action>_<model>`,
|
|
776
|
-
and the value should be set to the permission [constraints](../guides/permissions.md#
|
|
776
|
+
and the value should be set to the permission [constraints](../guides/permissions.md#example-constraint-definitions),
|
|
777
777
|
or `None` to allow all objects.
|
|
778
778
|
|
|
779
779
|
Example:
|
|
@@ -1278,7 +1278,7 @@ properties:
|
|
|
1278
1278
|
|
|
1279
1279
|
!!! note
|
|
1280
1280
|
If a given device has an appropriately populated
|
|
1281
|
-
[secrets group](../../platform-functionality/secret.md#
|
|
1281
|
+
[secrets group](../../platform-functionality/secret.md#secrets-groups) assigned to it,
|
|
1282
1282
|
the [secrets](../../platform-functionality/secret.md) defined in that group will take precedence
|
|
1283
1283
|
over these default values.
|
|
1284
1284
|
environment_variable: "NAUTOBOT_NAPALM_PASSWORD"
|
|
@@ -1300,7 +1300,7 @@ properties:
|
|
|
1300
1300
|
|
|
1301
1301
|
!!! note
|
|
1302
1302
|
If a given device has an appropriately populated
|
|
1303
|
-
[secrets group](../../platform-functionality/secret.md#
|
|
1303
|
+
[secrets group](../../platform-functionality/secret.md#secrets-groups) assigned to it,
|
|
1304
1304
|
the [secrets](../../platform-functionality/secret.md) defined in that group will take precedence
|
|
1305
1305
|
over these default values.
|
|
1306
1306
|
environment_variable: "NAUTOBOT_NAPALM_USERNAME"
|
nautobot/core/testing/filters.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import string
|
|
3
3
|
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
4
5
|
from django.db.models import Count, Q
|
|
5
6
|
from django.db.models.fields.related import ManyToManyField
|
|
6
7
|
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
|
|
7
8
|
from django.test import tag
|
|
8
9
|
|
|
9
|
-
from nautobot.core.filters import
|
|
10
|
+
from nautobot.core.filters import (
|
|
11
|
+
ContentTypeChoiceFilter,
|
|
12
|
+
ContentTypeFilter,
|
|
13
|
+
ContentTypeMultipleChoiceFilter,
|
|
14
|
+
RelatedMembershipBooleanFilter,
|
|
15
|
+
SearchFilter,
|
|
16
|
+
)
|
|
10
17
|
from nautobot.core.models.generics import PrimaryModel
|
|
11
18
|
from nautobot.core.testing import views
|
|
12
19
|
from nautobot.tenancy import models
|
|
@@ -286,6 +293,22 @@ class FilterTestCases:
|
|
|
286
293
|
obj, obj_field_name = self._get_nested_related_obj_and_its_field_name(obj, obj_field_name)
|
|
287
294
|
self._assert_q_filter_predicate_validity(obj, obj_field_name, filter_field_name, lookup_method)
|
|
288
295
|
|
|
296
|
+
def test_content_type_related_fields_uses_content_type_filter(self):
|
|
297
|
+
for field in self.queryset.model._meta.fields:
|
|
298
|
+
related_model = getattr(field, "related_model", None)
|
|
299
|
+
if not related_model or related_model != ContentType:
|
|
300
|
+
continue
|
|
301
|
+
with self.subTest(
|
|
302
|
+
f"Assert {self.filterset.__class__.__name__}.{field.name} implements ContentTypeFilter"
|
|
303
|
+
):
|
|
304
|
+
filter_field = self.filterset.get_filters().get(field.name)
|
|
305
|
+
if not filter_field:
|
|
306
|
+
# This field is not part of the Filterset.
|
|
307
|
+
continue
|
|
308
|
+
self.assertIsInstance(
|
|
309
|
+
filter_field, (ContentTypeFilter, ContentTypeMultipleChoiceFilter, ContentTypeChoiceFilter)
|
|
310
|
+
)
|
|
311
|
+
|
|
289
312
|
class NameOnlyFilterTestCase(FilterTestCase):
|
|
290
313
|
"""Add simple tests for filtering by name."""
|
|
291
314
|
|
nautobot/core/testing/views.py
CHANGED
|
@@ -213,6 +213,8 @@ class ViewTestCases:
|
|
|
213
213
|
escape(str(instance.cf.get(custom_field.key) or "")), response_body, msg=response_body
|
|
214
214
|
)
|
|
215
215
|
|
|
216
|
+
return response # for consumption by child test cases if desired
|
|
217
|
+
|
|
216
218
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
217
219
|
def test_get_object_with_constrained_permission(self):
|
|
218
220
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
@@ -230,11 +232,14 @@ class ViewTestCases:
|
|
|
230
232
|
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
231
233
|
|
|
232
234
|
# Try GET to permitted object
|
|
233
|
-
self.
|
|
235
|
+
response = self.client.get(instance1.get_absolute_url())
|
|
236
|
+
self.assertHttpStatus(response, 200)
|
|
234
237
|
|
|
235
238
|
# Try GET to non-permitted object
|
|
236
239
|
self.assertHttpStatus(self.client.get(instance2.get_absolute_url()), 404)
|
|
237
240
|
|
|
241
|
+
return response # for consumption by child test cases if desired
|
|
242
|
+
|
|
238
243
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
239
244
|
def test_has_advanced_tab(self):
|
|
240
245
|
instance = self._get_queryset().first()
|
|
@@ -740,6 +745,13 @@ class ViewTestCases:
|
|
|
740
745
|
def get_list_view(self):
|
|
741
746
|
return lookup.get_view_for_model(self.model, view_type="List")
|
|
742
747
|
|
|
748
|
+
def test_list_view_has_filter_form(self):
|
|
749
|
+
view = self.get_list_view()
|
|
750
|
+
if hasattr(view, "filterset_form"): # ObjectListView
|
|
751
|
+
self.assertIsNotNone(view.filterset_form, "List view lacks a FilterForm")
|
|
752
|
+
if hasattr(view, "filterset_form_class"): # ObjectListViewMixin
|
|
753
|
+
self.assertIsNotNone(view.filterset_form_class, "List viewset lacks a FilterForm")
|
|
754
|
+
|
|
743
755
|
def test_table_with_indentation_is_removed_on_filter_or_sort(self):
|
|
744
756
|
self.user.is_superuser = True
|
|
745
757
|
self.user.save()
|
nautobot/core/views/utils.py
CHANGED
|
@@ -215,11 +215,28 @@ def handle_protectederror(obj_list, request, e):
|
|
|
215
215
|
protected_count,
|
|
216
216
|
)
|
|
217
217
|
|
|
218
|
+
# Format objects based on whether they have a detail view/absolute url
|
|
219
|
+
objects_with_absolute_url = []
|
|
220
|
+
objects_without_absolute_url = []
|
|
218
221
|
# Append dependent objects to error message
|
|
222
|
+
for dependent in protected_objects[:50]:
|
|
223
|
+
try:
|
|
224
|
+
dependent.get_absolute_url()
|
|
225
|
+
objects_with_absolute_url.append(dependent)
|
|
226
|
+
except AttributeError:
|
|
227
|
+
objects_without_absolute_url.append(dependent)
|
|
228
|
+
|
|
219
229
|
err_message += format_html_join(
|
|
220
230
|
", ",
|
|
221
231
|
'<a href="{}">{}</a>',
|
|
222
|
-
((dependent.get_absolute_url(), dependent) for dependent in
|
|
232
|
+
((dependent.get_absolute_url(), dependent) for dependent in objects_with_absolute_url),
|
|
233
|
+
)
|
|
234
|
+
if objects_with_absolute_url and objects_without_absolute_url:
|
|
235
|
+
err_message += format_html(", ")
|
|
236
|
+
err_message += format_html_join(
|
|
237
|
+
", ",
|
|
238
|
+
"<span>{}</span>",
|
|
239
|
+
((dependent,) for dependent in objects_without_absolute_url),
|
|
223
240
|
)
|
|
224
241
|
|
|
225
242
|
messages.error(request, err_message)
|
|
@@ -102,13 +102,13 @@ __all__ = (
|
|
|
102
102
|
"ControllerManagedDeviceGroupFilterSet",
|
|
103
103
|
"DeviceBayFilterSet",
|
|
104
104
|
"DeviceBayTemplateFilterSet",
|
|
105
|
+
"DeviceFamilyFilterSet",
|
|
105
106
|
"DeviceFilterSet",
|
|
106
107
|
"DeviceRedundancyGroupFilterSet",
|
|
107
108
|
"DeviceTypeFilterSet",
|
|
108
109
|
"DeviceTypeToSoftwareImageFileFilterSet",
|
|
109
110
|
"FrontPortFilterSet",
|
|
110
111
|
"FrontPortTemplateFilterSet",
|
|
111
|
-
"DeviceFamilyFilterSet",
|
|
112
112
|
"InterfaceConnectionFilterSet",
|
|
113
113
|
"InterfaceFilterSet",
|
|
114
114
|
"InterfaceRedundancyGroupFilterSet",
|
nautobot/dcim/forms.py
CHANGED
|
@@ -728,6 +728,15 @@ class ManufacturerForm(NautobotModelForm):
|
|
|
728
728
|
]
|
|
729
729
|
|
|
730
730
|
|
|
731
|
+
class ManufacturerFilterForm(NautobotFilterForm):
|
|
732
|
+
model = Manufacturer
|
|
733
|
+
q = forms.CharField(required=False, label="Search")
|
|
734
|
+
device_types = DynamicModelMultipleChoiceField(
|
|
735
|
+
queryset=DeviceType.objects.all(), to_field_name="model", required=False
|
|
736
|
+
)
|
|
737
|
+
platforms = DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), to_field_name="name", required=False)
|
|
738
|
+
|
|
739
|
+
|
|
731
740
|
#
|
|
732
741
|
# Device Family
|
|
733
742
|
#
|
|
@@ -745,6 +754,9 @@ class DeviceFamilyForm(NautobotModelForm):
|
|
|
745
754
|
class DeviceFamilyFilterForm(NautobotFilterForm):
|
|
746
755
|
model = DeviceFamily
|
|
747
756
|
q = forms.CharField(required=False, label="Search")
|
|
757
|
+
device_types = DynamicModelMultipleChoiceField(
|
|
758
|
+
queryset=DeviceType.objects.all(), to_field_name="model", required=False
|
|
759
|
+
)
|
|
748
760
|
tags = TagFilterField(model)
|
|
749
761
|
|
|
750
762
|
|
|
@@ -1582,6 +1594,13 @@ class PlatformForm(NautobotModelForm):
|
|
|
1582
1594
|
}
|
|
1583
1595
|
|
|
1584
1596
|
|
|
1597
|
+
class PlatformFilterForm(NautobotFilterForm):
|
|
1598
|
+
model = Platform
|
|
1599
|
+
q = forms.CharField(required=False, label="Search")
|
|
1600
|
+
name = forms.CharField(required=False)
|
|
1601
|
+
network_driver = forms.CharField(required=False)
|
|
1602
|
+
|
|
1603
|
+
|
|
1585
1604
|
#
|
|
1586
1605
|
# Devices
|
|
1587
1606
|
#
|
|
@@ -2567,14 +2586,14 @@ class InterfaceBulkEditForm(
|
|
|
2567
2586
|
queryset=VLAN.objects.all(),
|
|
2568
2587
|
required=False,
|
|
2569
2588
|
query_params={
|
|
2570
|
-
"
|
|
2589
|
+
"locations": "null",
|
|
2571
2590
|
},
|
|
2572
2591
|
)
|
|
2573
2592
|
tagged_vlans = DynamicModelMultipleChoiceField(
|
|
2574
2593
|
queryset=VLAN.objects.all(),
|
|
2575
2594
|
required=False,
|
|
2576
2595
|
query_params={
|
|
2577
|
-
"
|
|
2596
|
+
"locations": "null",
|
|
2578
2597
|
},
|
|
2579
2598
|
)
|
|
2580
2599
|
vrf = DynamicModelChoiceField(
|
|
@@ -2618,8 +2637,8 @@ class InterfaceBulkEditForm(
|
|
|
2618
2637
|
# Limit VLAN choices by Location
|
|
2619
2638
|
if locations.count() == 1:
|
|
2620
2639
|
location = locations.first()
|
|
2621
|
-
self.fields["untagged_vlan"].widget.add_query_param("
|
|
2622
|
-
self.fields["tagged_vlans"].widget.add_query_param("
|
|
2640
|
+
self.fields["untagged_vlan"].widget.add_query_param("locations", location.pk)
|
|
2641
|
+
self.fields["tagged_vlans"].widget.add_query_param("locations", location.pk)
|
|
2623
2642
|
|
|
2624
2643
|
# Restrict parent/bridge/LAG interface assignment by device (or VC master)
|
|
2625
2644
|
if device_count == 1:
|
|
@@ -45,9 +45,15 @@ __all__ = (
|
|
|
45
45
|
class ManufacturerTable(BaseTable):
|
|
46
46
|
pk = ToggleColumn()
|
|
47
47
|
name = tables.LinkColumn()
|
|
48
|
-
device_type_count =
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
device_type_count = LinkedCountColumn(
|
|
49
|
+
viewname="dcim:devicetype_list", url_params={"manufacturer": "name"}, verbose_name="Device Types"
|
|
50
|
+
)
|
|
51
|
+
inventory_item_count = LinkedCountColumn(
|
|
52
|
+
viewname="dcim:inventoryitem_list", url_params={"manufacturer": "name"}, verbose_name="Inventory Items"
|
|
53
|
+
)
|
|
54
|
+
platform_count = LinkedCountColumn(
|
|
55
|
+
viewname="dcim:platform_list", url_params={"manufacturer": "name"}, verbose_name="Platforms"
|
|
56
|
+
)
|
|
51
57
|
actions = ButtonsColumn(Manufacturer)
|
|
52
58
|
|
|
53
59
|
class Meta(BaseTable.Meta):
|
|
@@ -71,7 +77,9 @@ class ManufacturerTable(BaseTable):
|
|
|
71
77
|
class DeviceFamilyTable(BaseTable):
|
|
72
78
|
pk = ToggleColumn()
|
|
73
79
|
name = tables.Column(linkify=True)
|
|
74
|
-
device_type_count =
|
|
80
|
+
device_type_count = LinkedCountColumn(
|
|
81
|
+
viewname="dcim:devicetype_list", url_params={"device_family": "name"}, verbose_name="Device Types"
|
|
82
|
+
)
|
|
75
83
|
actions = ButtonsColumn(DeviceFamily)
|
|
76
84
|
tags = TagColumn(url_name="dcim:devicefamily_list")
|
|
77
85
|
|
|
@@ -95,6 +103,8 @@ class DeviceFamilyTable(BaseTable):
|
|
|
95
103
|
class DeviceTypeTable(BaseTable):
|
|
96
104
|
pk = ToggleColumn()
|
|
97
105
|
model = tables.Column(linkify=True, verbose_name="Device Type")
|
|
106
|
+
manufacturer = tables.Column(linkify=True)
|
|
107
|
+
device_family = tables.Column(linkify=True)
|
|
98
108
|
is_full_depth = BooleanColumn(verbose_name="Full Depth")
|
|
99
109
|
device_count = LinkedCountColumn(
|
|
100
110
|
viewname="dcim:device_list",
|
|
@@ -109,6 +119,7 @@ class DeviceTypeTable(BaseTable):
|
|
|
109
119
|
"pk",
|
|
110
120
|
"model",
|
|
111
121
|
"manufacturer",
|
|
122
|
+
"device_family",
|
|
112
123
|
"part_number",
|
|
113
124
|
"u_height",
|
|
114
125
|
"is_full_depth",
|
|
@@ -3398,6 +3398,48 @@ class SoftwareImageFileTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3398
3398
|
"download_url": "https://example.com/software_image_file_test_case.bin",
|
|
3399
3399
|
}
|
|
3400
3400
|
|
|
3401
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3402
|
+
def test_correct_handling_for_model_protected_error(self):
|
|
3403
|
+
platform = Platform.objects.first()
|
|
3404
|
+
software_version_status = Status.objects.get_for_model(SoftwareVersion).first()
|
|
3405
|
+
software_image_file_status = Status.objects.get_for_model(SoftwareImageFile).first()
|
|
3406
|
+
software_version = SoftwareVersion.objects.create(
|
|
3407
|
+
platform=platform, version="Test version 1.0.0", status=software_version_status
|
|
3408
|
+
)
|
|
3409
|
+
software_image_file = SoftwareImageFile.objects.create(
|
|
3410
|
+
software_version=software_version,
|
|
3411
|
+
image_file_name="software_image_file_qs_test_1.bin",
|
|
3412
|
+
status=software_image_file_status,
|
|
3413
|
+
)
|
|
3414
|
+
device_type = DeviceType.objects.first()
|
|
3415
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
3416
|
+
device_status = Status.objects.get_for_model(Device).first()
|
|
3417
|
+
location = Location.objects.filter(location_type__name="Campus").first()
|
|
3418
|
+
Device.objects.create(
|
|
3419
|
+
device_type=device_type,
|
|
3420
|
+
role=device_role,
|
|
3421
|
+
name="Device 1",
|
|
3422
|
+
location=location,
|
|
3423
|
+
status=device_status,
|
|
3424
|
+
software_version=software_version,
|
|
3425
|
+
)
|
|
3426
|
+
device_type_to_software_image_file = DeviceTypeToSoftwareImageFile.objects.create(
|
|
3427
|
+
device_type=device_type, software_image_file=software_image_file
|
|
3428
|
+
)
|
|
3429
|
+
|
|
3430
|
+
self.add_permissions("dcim.delete_softwareimagefile")
|
|
3431
|
+
pk_list = [software_image_file.pk]
|
|
3432
|
+
data = {
|
|
3433
|
+
"pk": pk_list,
|
|
3434
|
+
"confirm": True,
|
|
3435
|
+
"_confirm": True, # Form button
|
|
3436
|
+
}
|
|
3437
|
+
response = self.client.post(self._get_url("bulk_delete"), data, follow=True)
|
|
3438
|
+
self.assertHttpStatus(response, 200)
|
|
3439
|
+
response_body = response.content.decode(response.charset)
|
|
3440
|
+
# Assert protected error message included in the response body
|
|
3441
|
+
self.assertInHTML(f"<span>{device_type_to_software_image_file}</span>", response_body)
|
|
3442
|
+
|
|
3401
3443
|
|
|
3402
3444
|
class SoftwareVersionTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
3403
3445
|
model = SoftwareVersion
|
|
@@ -3436,6 +3478,48 @@ class SoftwareVersionTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3436
3478
|
"pre_release": True,
|
|
3437
3479
|
}
|
|
3438
3480
|
|
|
3481
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3482
|
+
def test_correct_handling_for_model_protected_error(self):
|
|
3483
|
+
platform = Platform.objects.first()
|
|
3484
|
+
software_version_status = Status.objects.get_for_model(SoftwareVersion).first()
|
|
3485
|
+
software_image_file_status = Status.objects.get_for_model(SoftwareImageFile).first()
|
|
3486
|
+
software_version = SoftwareVersion.objects.create(
|
|
3487
|
+
platform=platform, version="Test version 1.0.0", status=software_version_status
|
|
3488
|
+
)
|
|
3489
|
+
software_image_file = SoftwareImageFile.objects.create(
|
|
3490
|
+
software_version=software_version,
|
|
3491
|
+
image_file_name="software_image_file_qs_test_1.bin",
|
|
3492
|
+
status=software_image_file_status,
|
|
3493
|
+
)
|
|
3494
|
+
device_type = DeviceType.objects.first()
|
|
3495
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
3496
|
+
device_status = Status.objects.get_for_model(Device).first()
|
|
3497
|
+
location = Location.objects.filter(location_type__name="Campus").first()
|
|
3498
|
+
Device.objects.create(
|
|
3499
|
+
device_type=device_type,
|
|
3500
|
+
role=device_role,
|
|
3501
|
+
name="Device 1",
|
|
3502
|
+
location=location,
|
|
3503
|
+
status=device_status,
|
|
3504
|
+
software_version=software_version,
|
|
3505
|
+
)
|
|
3506
|
+
device_type_to_software_image_file = DeviceTypeToSoftwareImageFile.objects.create(
|
|
3507
|
+
device_type=device_type, software_image_file=software_image_file
|
|
3508
|
+
)
|
|
3509
|
+
|
|
3510
|
+
self.add_permissions("dcim.delete_softwareversion")
|
|
3511
|
+
pk_list = [software_version.pk]
|
|
3512
|
+
data = {
|
|
3513
|
+
"pk": pk_list,
|
|
3514
|
+
"confirm": True,
|
|
3515
|
+
"_confirm": True, # Form button
|
|
3516
|
+
}
|
|
3517
|
+
response = self.client.post(self._get_url("bulk_delete"), data, follow=True)
|
|
3518
|
+
self.assertHttpStatus(response, 200)
|
|
3519
|
+
response_body = response.content.decode(response.charset)
|
|
3520
|
+
# Assert protected error message included in the response body
|
|
3521
|
+
self.assertInHTML(f"<span>{device_type_to_software_image_file}</span>", response_body)
|
|
3522
|
+
|
|
3439
3523
|
|
|
3440
3524
|
class ControllerTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
3441
3525
|
model = Controller
|
nautobot/dcim/views.py
CHANGED
|
@@ -710,6 +710,7 @@ class ManufacturerListView(generic.ObjectListView):
|
|
|
710
710
|
platform_count=count_related(Platform, "manufacturer"),
|
|
711
711
|
)
|
|
712
712
|
filterset = filters.ManufacturerFilterSet
|
|
713
|
+
filterset_form = forms.ManufacturerFilterForm
|
|
713
714
|
table = tables.ManufacturerTable
|
|
714
715
|
|
|
715
716
|
|
|
@@ -1212,6 +1213,7 @@ class PlatformListView(generic.ObjectListView):
|
|
|
1212
1213
|
virtual_machine_count=count_related(VirtualMachine, "platform"),
|
|
1213
1214
|
)
|
|
1214
1215
|
filterset = filters.PlatformFilterSet
|
|
1216
|
+
filterset_form = forms.PlatformFilterForm
|
|
1215
1217
|
table = tables.PlatformTable
|
|
1216
1218
|
|
|
1217
1219
|
|
|
@@ -3103,6 +3105,7 @@ class InterfaceRedundancyGroupAssociationUIViewSet(ObjectEditViewMixin, ObjectDe
|
|
|
3103
3105
|
|
|
3104
3106
|
class DeviceFamilyUIViewSet(NautobotUIViewSet):
|
|
3105
3107
|
filterset_class = filters.DeviceFamilyFilterSet
|
|
3108
|
+
filterset_form_class = forms.DeviceFamilyFilterForm
|
|
3106
3109
|
form_class = forms.DeviceFamilyForm
|
|
3107
3110
|
bulk_update_form_class = forms.DeviceFamilyBulkEditForm
|
|
3108
3111
|
queryset = DeviceFamily.objects.annotate(device_type_count=count_related(DeviceType, "device_family"))
|
nautobot/extras/api/views.py
CHANGED
|
@@ -304,13 +304,13 @@ class DynamicGroupViewSet(NotesViewSetMixin, ModelViewSet):
|
|
|
304
304
|
# @extend_schema(methods=["get"], responses={200: member_response})
|
|
305
305
|
@action(detail=True, methods=["get"])
|
|
306
306
|
def members(self, request, pk, *args, **kwargs):
|
|
307
|
-
"""List member objects of
|
|
307
|
+
"""List the member objects of this dynamic group."""
|
|
308
308
|
instance = get_object_or_404(self.queryset, pk=pk)
|
|
309
309
|
|
|
310
310
|
# Retrieve the serializer for the content_type and paginate the results
|
|
311
311
|
member_model_class = instance.content_type.model_class()
|
|
312
312
|
member_serializer_class = get_serializer_for_model(member_model_class)
|
|
313
|
-
members = self.paginate_queryset(instance.members)
|
|
313
|
+
members = self.paginate_queryset(instance.members.restrict(request.user, "view"))
|
|
314
314
|
member_serializer = member_serializer_class(members, many=True, context={"request": request})
|
|
315
315
|
return self.get_paginated_response(member_serializer.data)
|
|
316
316
|
|
|
@@ -91,9 +91,12 @@ class ChangeContext:
|
|
|
91
91
|
for entry in self.deferred_object_changes[key]:
|
|
92
92
|
objectchange = entry["instance"].to_objectchange(entry["action"])
|
|
93
93
|
objectchange.user = entry["user"]
|
|
94
|
+
objectchange.user_name = objectchange.user.username
|
|
94
95
|
objectchange.request_id = self.change_id
|
|
95
96
|
objectchange.change_context = self.context
|
|
96
97
|
objectchange.change_context_detail = self.context_detail[:CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL]
|
|
98
|
+
if not objectchange.changed_object_id:
|
|
99
|
+
objectchange.changed_object_id = entry.get("changed_object_id")
|
|
97
100
|
create_object_changes.append(objectchange)
|
|
98
101
|
self.deferred_object_changes.pop(key, None)
|
|
99
102
|
ObjectChange.objects.bulk_create(create_object_changes, batch_size=batch_size)
|
|
@@ -504,7 +504,7 @@ class ContactFilterSet(ContactTeamFilterSet):
|
|
|
504
504
|
fields = "__all__"
|
|
505
505
|
|
|
506
506
|
|
|
507
|
-
class ContactAssociationFilterSet(NautobotFilterSet):
|
|
507
|
+
class ContactAssociationFilterSet(NautobotFilterSet, StatusModelFilterSetMixin, RoleModelFilterSetMixin):
|
|
508
508
|
q = SearchFilter(
|
|
509
509
|
filter_predicates={
|
|
510
510
|
"contact__name": "icontains",
|
|
@@ -512,6 +512,19 @@ class ContactAssociationFilterSet(NautobotFilterSet):
|
|
|
512
512
|
},
|
|
513
513
|
)
|
|
514
514
|
|
|
515
|
+
contact = NaturalKeyOrPKMultipleChoiceFilter(
|
|
516
|
+
queryset=Contact.objects.all(),
|
|
517
|
+
to_field_name="name",
|
|
518
|
+
label="Contact (name or ID)",
|
|
519
|
+
)
|
|
520
|
+
team = NaturalKeyOrPKMultipleChoiceFilter(
|
|
521
|
+
queryset=Team.objects.all(),
|
|
522
|
+
to_field_name="name",
|
|
523
|
+
label="Team (name or ID)",
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
associated_object_type = ContentTypeFilter()
|
|
527
|
+
|
|
515
528
|
class Meta:
|
|
516
529
|
model = ContactAssociation
|
|
517
530
|
fields = "__all__"
|
|
@@ -610,6 +623,7 @@ class ExportTemplateFilterSet(BaseFilterSet):
|
|
|
610
623
|
},
|
|
611
624
|
)
|
|
612
625
|
owner_content_type = ContentTypeFilter()
|
|
626
|
+
content_type = ContentTypeFilter()
|
|
613
627
|
|
|
614
628
|
class Meta:
|
|
615
629
|
model = ExportTemplate
|
nautobot/extras/forms/forms.py
CHANGED
|
@@ -104,6 +104,7 @@ __all__ = (
|
|
|
104
104
|
"ConfigContextSchemaBulkEditForm",
|
|
105
105
|
"ConfigContextSchemaFilterForm",
|
|
106
106
|
"CustomFieldForm",
|
|
107
|
+
"CustomFieldFilterForm",
|
|
107
108
|
"CustomFieldModelCSVForm",
|
|
108
109
|
"CustomFieldBulkCreateForm", # 2.0 TODO remove this deprecated class
|
|
109
110
|
"CustomFieldChoiceFormSet",
|
|
@@ -114,6 +115,7 @@ __all__ = (
|
|
|
114
115
|
"DynamicGroupMembershipFormSet",
|
|
115
116
|
"ExportTemplateForm",
|
|
116
117
|
"ExportTemplateFilterForm",
|
|
118
|
+
"ExternalIntegrationFilterForm",
|
|
117
119
|
"ExternalIntegrationForm",
|
|
118
120
|
"ExternalIntegrationBulkEditForm",
|
|
119
121
|
"GitRepositoryForm",
|
|
@@ -144,6 +146,7 @@ __all__ = (
|
|
|
144
146
|
"RelationshipFilterForm",
|
|
145
147
|
"RelationshipAssociationFilterForm",
|
|
146
148
|
"RoleBulkEditForm",
|
|
149
|
+
"RoleFilterForm",
|
|
147
150
|
"RoleForm",
|
|
148
151
|
"ScheduledJobFilterForm",
|
|
149
152
|
"SecretForm",
|
|
@@ -421,6 +424,17 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|
|
421
424
|
self.fields["key"].widget.attrs["readonly"] = True
|
|
422
425
|
|
|
423
426
|
|
|
427
|
+
class CustomFieldFilterForm(NautobotFilterForm):
|
|
428
|
+
model = CustomField
|
|
429
|
+
q = forms.CharField(required=False, label="Search")
|
|
430
|
+
content_types = MultipleContentTypeField(
|
|
431
|
+
queryset=ContentType.objects.filter(FeatureQuery("custom_fields").get_query()),
|
|
432
|
+
choices_as_strings=True,
|
|
433
|
+
required=False,
|
|
434
|
+
label="Content Type(s)",
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
424
438
|
class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelFormMixin):
|
|
425
439
|
"""
|
|
426
440
|
Base class for CSV/JSON/YAML import of models that support custom fields.
|
|
@@ -638,6 +652,14 @@ class ExternalIntegrationBulkEditForm(NautobotBulkEditForm):
|
|
|
638
652
|
nullable_fields = ["extra_config", "secrets_group", "headers"]
|
|
639
653
|
|
|
640
654
|
|
|
655
|
+
class ExternalIntegrationFilterForm(NautobotFilterForm):
|
|
656
|
+
model = ExternalIntegration
|
|
657
|
+
q = forms.CharField(required=False, label="Search")
|
|
658
|
+
secrets_group = DynamicModelMultipleChoiceField(
|
|
659
|
+
queryset=SecretsGroup.objects.all(), to_field_name="name", required=False
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
|
|
641
663
|
#
|
|
642
664
|
# Git repositories and other data sources
|
|
643
665
|
#
|
|
@@ -1429,6 +1451,17 @@ class RoleBulkEditForm(NautobotBulkEditForm):
|
|
|
1429
1451
|
nullable_fields = ["weight"]
|
|
1430
1452
|
|
|
1431
1453
|
|
|
1454
|
+
class RoleFilterForm(NautobotFilterForm):
|
|
1455
|
+
model = Role
|
|
1456
|
+
q = forms.CharField(required=False, label="Search")
|
|
1457
|
+
content_types = MultipleContentTypeField(
|
|
1458
|
+
queryset=RoleModelsQuery().as_queryset(),
|
|
1459
|
+
required=False,
|
|
1460
|
+
choices_as_strings=True,
|
|
1461
|
+
label="Content Type(s)",
|
|
1462
|
+
)
|
|
1463
|
+
|
|
1464
|
+
|
|
1432
1465
|
#
|
|
1433
1466
|
# Secrets
|
|
1434
1467
|
#
|
nautobot/extras/forms/mixins.py
CHANGED
|
@@ -45,7 +45,6 @@ __all__ = (
|
|
|
45
45
|
# 2.0 TODO: remove the below deprecated aliases
|
|
46
46
|
"AddRemoveTagsForm",
|
|
47
47
|
"CustomFieldBulkEditForm",
|
|
48
|
-
"CustomFieldFilterForm",
|
|
49
48
|
"CustomFieldModelForm",
|
|
50
49
|
"RelationshipModelForm",
|
|
51
50
|
"RoleModelBulkEditFormMixin",
|
|
@@ -767,11 +766,6 @@ class CustomFieldBulkEditForm(CustomFieldModelBulkEditFormMixin):
|
|
|
767
766
|
pass
|
|
768
767
|
|
|
769
768
|
|
|
770
|
-
@class_deprecated_in_favor_of(CustomFieldModelFilterFormMixin)
|
|
771
|
-
class CustomFieldFilterForm(CustomFieldModelFilterFormMixin):
|
|
772
|
-
pass
|
|
773
|
-
|
|
774
|
-
|
|
775
769
|
@class_deprecated_in_favor_of(CustomFieldModelFormMixin)
|
|
776
770
|
class CustomFieldModelForm(CustomFieldModelFormMixin):
|
|
777
771
|
pass
|
nautobot/extras/signals.py
CHANGED
|
@@ -246,7 +246,12 @@ def _handle_deleted_object(sender, instance, **kwargs):
|
|
|
246
246
|
|
|
247
247
|
if save_new_objectchange:
|
|
248
248
|
change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
|
|
249
|
-
{
|
|
249
|
+
{
|
|
250
|
+
"action": ObjectChangeActionChoices.ACTION_DELETE,
|
|
251
|
+
"instance": instance,
|
|
252
|
+
"user": user,
|
|
253
|
+
"changed_object_id": instance.pk,
|
|
254
|
+
}
|
|
250
255
|
)
|
|
251
256
|
if not change_context.defer_object_changes:
|
|
252
257
|
objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
|