nautobot 2.3.15b1__py3-none-any.whl → 2.3.16__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/views.py +3 -3
- nautobot/cloud/models.py +1 -1
- nautobot/core/api/fields.py +5 -5
- nautobot/core/api/serializers.py +9 -9
- nautobot/core/api/views.py +3 -2
- nautobot/core/apps/__init__.py +5 -2
- nautobot/core/celery/schedulers.py +1 -1
- nautobot/core/filters.py +19 -16
- nautobot/core/forms/fields.py +5 -5
- nautobot/core/graphql/types.py +1 -1
- nautobot/core/jobs/__init__.py +4 -4
- nautobot/core/jobs/cleanup.py +1 -1
- nautobot/core/jobs/groups.py +1 -1
- nautobot/core/management/commands/validate_models.py +1 -1
- nautobot/core/models/__init__.py +1 -1
- nautobot/core/models/query_functions.py +2 -2
- nautobot/core/models/tree_queries.py +2 -2
- nautobot/core/settings.py +3 -0
- nautobot/core/settings.yaml +8 -0
- nautobot/core/tables.py +5 -5
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/nautobot_config.py.j2 +3 -0
- nautobot/core/testing/filters.py +7 -3
- nautobot/core/testing/views.py +5 -0
- nautobot/core/tests/integration/test_app_home.py +0 -1
- nautobot/core/tests/integration/test_app_navbar.py +0 -1
- nautobot/core/tests/integration/test_filters.py +0 -2
- nautobot/core/tests/integration/test_home.py +0 -1
- nautobot/core/tests/integration/test_navbar.py +0 -1
- nautobot/core/tests/integration/test_view_authentication.py +1 -0
- nautobot/core/tests/runner.py +1 -1
- nautobot/core/tests/test_views.py +29 -0
- nautobot/core/urls.py +9 -0
- nautobot/core/views/generic.py +51 -43
- nautobot/core/views/mixins.py +21 -11
- nautobot/dcim/api/serializers.py +48 -48
- nautobot/dcim/forms.py +2 -0
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +22 -20
- nautobot/dcim/models/devices.py +1 -1
- nautobot/dcim/models/locations.py +3 -3
- nautobot/dcim/models/power.py +6 -5
- nautobot/dcim/models/racks.py +4 -4
- nautobot/dcim/tables/__init__.py +3 -3
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/tests/test_filters.py +1 -0
- nautobot/dcim/tests/test_graphql.py +52 -0
- nautobot/dcim/tests/test_models.py +4 -1
- nautobot/dcim/views.py +1 -1
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +72 -69
- nautobot/extras/api/views.py +4 -4
- nautobot/extras/forms/mixins.py +1 -1
- nautobot/extras/health_checks.py +1 -2
- nautobot/extras/jobs.py +5 -5
- nautobot/extras/managers.py +3 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
- nautobot/extras/models/customfields.py +12 -11
- nautobot/extras/models/groups.py +13 -9
- nautobot/extras/models/jobs.py +4 -4
- nautobot/extras/models/models.py +2 -2
- nautobot/extras/plugins/views.py +1 -1
- nautobot/extras/tables.py +5 -5
- nautobot/extras/test_jobs/api_test_job.py +1 -1
- nautobot/extras/test_jobs/atomic_transaction.py +2 -2
- nautobot/extras/test_jobs/dry_run.py +1 -1
- nautobot/extras/test_jobs/fail.py +5 -5
- nautobot/extras/test_jobs/file_output.py +1 -1
- nautobot/extras/test_jobs/file_upload_fail.py +1 -1
- nautobot/extras/test_jobs/file_upload_pass.py +1 -1
- nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
- nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
- nautobot/extras/test_jobs/log_redaction.py +1 -1
- nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
- nautobot/extras/test_jobs/modify_db.py +1 -1
- nautobot/extras/test_jobs/object_var_optional.py +1 -1
- nautobot/extras/test_jobs/object_var_required.py +1 -1
- nautobot/extras/test_jobs/object_vars.py +1 -1
- nautobot/extras/test_jobs/pass.py +3 -3
- nautobot/extras/test_jobs/profiling.py +1 -1
- nautobot/extras/test_jobs/relative_import.py +3 -3
- nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
- nautobot/extras/test_jobs/task_queues.py +1 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
- nautobot/extras/tests/test_api.py +13 -13
- nautobot/extras/tests/test_customfields.py +1 -1
- nautobot/extras/tests/test_datasources.py +2 -1
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_filters.py +6 -6
- nautobot/extras/tests/test_forms.py +20 -1
- nautobot/extras/tests/test_jobs.py +11 -11
- nautobot/extras/tests/test_models.py +10 -10
- nautobot/extras/tests/test_relationships.py +1 -1
- nautobot/extras/tests/test_views.py +16 -16
- nautobot/extras/views.py +20 -16
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +33 -33
- nautobot/ipam/api/views.py +37 -61
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tests/test_api.py +12 -1
- nautobot/ipam/tests/test_forms.py +51 -47
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_querysets.py +14 -0
- nautobot/project-static/docs/404.html +2 -2
- nautobot/project-static/docs/apps/index.html +2 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +4 -4
- nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
- nautobot/project-static/docs/development/apps/api/setup.html +2 -2
- nautobot/project-static/docs/development/apps/api/testing.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
- nautobot/project-static/docs/development/apps/index.html +2 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
- nautobot/project-static/docs/development/core/application-registry.html +2 -2
- nautobot/project-static/docs/development/core/best-practices.html +2 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
- nautobot/project-static/docs/development/core/caching.html +2 -2
- nautobot/project-static/docs/development/core/controllers.html +2 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +27 -70
- nautobot/project-static/docs/development/core/generic-views.html +2 -2
- nautobot/project-static/docs/development/core/getting-started.html +2 -2
- nautobot/project-static/docs/development/core/homepage.html +2 -2
- nautobot/project-static/docs/development/core/index.html +2 -2
- nautobot/project-static/docs/development/core/model-checklist.html +2 -2
- nautobot/project-static/docs/development/core/model-features.html +2 -2
- nautobot/project-static/docs/development/core/natural-keys.html +2 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
- nautobot/project-static/docs/development/core/release-checklist.html +2 -2
- nautobot/project-static/docs/development/core/role-internals.html +2 -2
- nautobot/project-static/docs/development/core/settings.html +2 -2
- nautobot/project-static/docs/development/core/style-guide.html +2 -2
- nautobot/project-static/docs/development/core/templates.html +2 -2
- nautobot/project-static/docs/development/core/testing.html +2 -2
- nautobot/project-static/docs/development/core/user-preferences.html +2 -2
- nautobot/project-static/docs/development/index.html +2 -2
- nautobot/project-static/docs/development/jobs/index.html +2 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
- nautobot/project-static/docs/index.html +2 -2
- nautobot/project-static/docs/overview/application_stack.html +2 -2
- nautobot/project-static/docs/overview/design_philosophy.html +2 -2
- nautobot/project-static/docs/release-notes/index.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.3.html +471 -220
- nautobot/project-static/docs/requirements.txt +2 -2
- 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 +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
- nautobot/project-static/docs/user-guide/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +4 -4
- nautobot/users/api/views.py +1 -1
- nautobot/virtualization/api/serializers.py +4 -4
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/METADATA +5 -4
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/RECORD +392 -393
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/WHEEL +1 -1
- nautobot/core/fixtures/user-data.json +0 -59
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/NOTICE +0 -0
- {nautobot-2.3.15b1.dist-info → nautobot-2.3.16.dist-info}/entry_points.txt +0 -0
|
@@ -594,3 +594,32 @@ class ExampleViewWithCustomPermissionsTest(TestCase):
|
|
|
594
594
|
self.user.save()
|
|
595
595
|
response = self.client.get(url)
|
|
596
596
|
self.assertBodyContains(response, "You are viewing a table of example models")
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
class SearchRobotsTestCase(TestCase):
|
|
600
|
+
def test_robots_disallowed(self):
|
|
601
|
+
"""
|
|
602
|
+
Test that the robots.txt file is accessible to all users and defaults to disallowing all bots.
|
|
603
|
+
"""
|
|
604
|
+
url = reverse("robots_txt")
|
|
605
|
+
response = self.client.get(url)
|
|
606
|
+
self.assertHttpStatus(response, 200)
|
|
607
|
+
self.assertBodyContains(response, "User-Agent: *")
|
|
608
|
+
self.assertBodyContains(response, "Disallow: /")
|
|
609
|
+
|
|
610
|
+
url = reverse("home")
|
|
611
|
+
response = self.client.get(url)
|
|
612
|
+
self.assertContains(response, '<meta name="robots" content="noindex, nofollow">', html=True)
|
|
613
|
+
|
|
614
|
+
@override_settings(PUBLISH_ROBOTS_TXT=False)
|
|
615
|
+
def test_robots_allowed(self):
|
|
616
|
+
"""
|
|
617
|
+
Test that the robots.txt file is not published if PUBLISH_ROBOTS_TXT is set to False.
|
|
618
|
+
"""
|
|
619
|
+
url = reverse("robots_txt")
|
|
620
|
+
response = self.client.get(url)
|
|
621
|
+
self.assertHttpStatus(response, 404)
|
|
622
|
+
|
|
623
|
+
url = reverse("home")
|
|
624
|
+
response = self.client.get(url)
|
|
625
|
+
self.assertNotContains(response, '<meta name="robots" content="noindex, nofollow">', html=True)
|
nautobot/core/urls.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
|
+
from django.http import HttpResponse, HttpResponseNotFound
|
|
2
3
|
from django.urls import include, path
|
|
3
4
|
from django.views.generic import TemplateView
|
|
4
5
|
from django.views.static import serve
|
|
@@ -72,6 +73,14 @@ urlpatterns = [
|
|
|
72
73
|
path(
|
|
73
74
|
"template.css", TemplateView.as_view(template_name="template.css", content_type="text/css"), name="template_css"
|
|
74
75
|
),
|
|
76
|
+
# The response is conditional as opposed to wrapping the path() call in an if statement to be able to test the setting with current test setup
|
|
77
|
+
path(
|
|
78
|
+
"robots.txt",
|
|
79
|
+
lambda x: HttpResponse("User-Agent: *\nDisallow: /", content_type="text/plain")
|
|
80
|
+
if settings.PUBLISH_ROBOTS_TXT
|
|
81
|
+
else HttpResponseNotFound(),
|
|
82
|
+
name="robots_txt",
|
|
83
|
+
),
|
|
75
84
|
]
|
|
76
85
|
|
|
77
86
|
|
nautobot/core/views/generic.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations # python 3.8
|
|
2
|
+
|
|
1
3
|
from copy import deepcopy
|
|
2
4
|
import logging
|
|
3
5
|
import re
|
|
6
|
+
from typing import Optional
|
|
4
7
|
|
|
5
8
|
from django.conf import settings
|
|
6
9
|
from django.contrib import messages
|
|
@@ -13,7 +16,7 @@ from django.core.exceptions import (
|
|
|
13
16
|
ValidationError,
|
|
14
17
|
)
|
|
15
18
|
from django.db import IntegrityError, transaction
|
|
16
|
-
from django.db.models import ManyToManyField, ProtectedError, Q
|
|
19
|
+
from django.db.models import ManyToManyField, Model, ProtectedError, Q, QuerySet
|
|
17
20
|
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
18
21
|
from django.http import HttpResponse
|
|
19
22
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
@@ -22,7 +25,8 @@ from django.utils.encoding import iri_to_uri
|
|
|
22
25
|
from django.utils.html import format_html
|
|
23
26
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
24
27
|
from django.views.generic import View
|
|
25
|
-
from
|
|
28
|
+
from django_filters import FilterSet
|
|
29
|
+
from django_tables2 import RequestConfig, Table
|
|
26
30
|
|
|
27
31
|
from nautobot.core.api.utils import get_serializer_for_model
|
|
28
32
|
from nautobot.core.exceptions import AbortTransaction
|
|
@@ -78,8 +82,8 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
78
82
|
template_name: Name of the template to use
|
|
79
83
|
"""
|
|
80
84
|
|
|
81
|
-
queryset
|
|
82
|
-
template_name = None
|
|
85
|
+
queryset: QuerySet
|
|
86
|
+
template_name: Optional[str] = None
|
|
83
87
|
|
|
84
88
|
def get_required_permission(self):
|
|
85
89
|
return get_permission_for_model(self.queryset.model, "view")
|
|
@@ -139,10 +143,10 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
139
143
|
non_filter_params: List of query parameters that are **not** used for queryset filtering
|
|
140
144
|
"""
|
|
141
145
|
|
|
142
|
-
queryset
|
|
143
|
-
filterset = None
|
|
144
|
-
filterset_form = None
|
|
145
|
-
table = None
|
|
146
|
+
queryset: QuerySet
|
|
147
|
+
filterset: Optional[type[FilterSet]] = None
|
|
148
|
+
filterset_form: Optional[type[Form]] = None
|
|
149
|
+
table: Optional[type[Table]] = None
|
|
146
150
|
template_name = "generic/object_list.html"
|
|
147
151
|
action_buttons = ("add", "import", "export")
|
|
148
152
|
non_filter_params = (
|
|
@@ -159,7 +163,11 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
159
163
|
def get_filter_params(self, request):
|
|
160
164
|
"""Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
|
|
161
165
|
params = request.GET.copy()
|
|
162
|
-
filter_params = get_filterable_params_from_filter_params(
|
|
166
|
+
filter_params = get_filterable_params_from_filter_params(
|
|
167
|
+
params,
|
|
168
|
+
self.non_filter_params,
|
|
169
|
+
self.filterset(), # pylint: disable=not-callable # this fn is only called if filterset is not None
|
|
170
|
+
)
|
|
163
171
|
if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
|
|
164
172
|
return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
|
|
165
173
|
return filter_params
|
|
@@ -233,9 +241,9 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
233
241
|
except ObjectDoesNotExist:
|
|
234
242
|
pass
|
|
235
243
|
|
|
236
|
-
if self.filterset:
|
|
244
|
+
if self.filterset is not None:
|
|
237
245
|
filter_params = self.get_filter_params(request)
|
|
238
|
-
filterset = self.filterset(filter_params, self.queryset)
|
|
246
|
+
filterset = self.filterset(filter_params, self.queryset) # pylint:disable=not-callable # only if not None
|
|
239
247
|
self.queryset = filterset.qs
|
|
240
248
|
if not filterset.is_valid():
|
|
241
249
|
messages.error(
|
|
@@ -263,7 +271,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
263
271
|
else:
|
|
264
272
|
dynamic_filter_form = DynamicFilterFormSet(filterset=filterset)
|
|
265
273
|
|
|
266
|
-
if self.filterset_form:
|
|
274
|
+
if self.filterset_form is not None:
|
|
267
275
|
filter_form = self.filterset_form(filter_params, label_suffix="")
|
|
268
276
|
|
|
269
277
|
# Check for export template rendering
|
|
@@ -323,14 +331,14 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
323
331
|
messages.error(request, f"Saved view {current_saved_view_pk} not found")
|
|
324
332
|
|
|
325
333
|
# Construct the objects table
|
|
326
|
-
if self.table:
|
|
334
|
+
if self.table is not None:
|
|
327
335
|
if self.request.GET.getlist("sort") or (
|
|
328
336
|
current_saved_view is not None and current_saved_view.config.get("sort_order")
|
|
329
337
|
):
|
|
330
338
|
hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
|
|
331
339
|
table_changes_pending = self.request.GET.get("table_changes_pending", False)
|
|
332
340
|
|
|
333
|
-
table = self.table(
|
|
341
|
+
table = self.table( # pylint: disable=not-callable # we confirmed that self.table is not None
|
|
334
342
|
self.queryset,
|
|
335
343
|
table_changes_pending=table_changes_pending,
|
|
336
344
|
saved_view=current_saved_view,
|
|
@@ -407,8 +415,8 @@ class ObjectEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
407
415
|
template_name: The name of the template
|
|
408
416
|
"""
|
|
409
417
|
|
|
410
|
-
queryset
|
|
411
|
-
model_form
|
|
418
|
+
queryset: QuerySet
|
|
419
|
+
model_form: type[Form]
|
|
412
420
|
template_name = "generic/object_create.html"
|
|
413
421
|
|
|
414
422
|
def get_required_permission(self):
|
|
@@ -550,7 +558,7 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
550
558
|
template_name: The name of the template
|
|
551
559
|
"""
|
|
552
560
|
|
|
553
|
-
queryset
|
|
561
|
+
queryset: QuerySet
|
|
554
562
|
template_name = "generic/object_delete.html"
|
|
555
563
|
|
|
556
564
|
def get_required_permission(self):
|
|
@@ -630,9 +638,9 @@ class BulkCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
630
638
|
template_name: The name of the template
|
|
631
639
|
"""
|
|
632
640
|
|
|
633
|
-
queryset
|
|
634
|
-
form
|
|
635
|
-
model_form
|
|
641
|
+
queryset: QuerySet
|
|
642
|
+
form: type[Form]
|
|
643
|
+
model_form: type[Form]
|
|
636
644
|
pattern_target = ""
|
|
637
645
|
template_name = None
|
|
638
646
|
|
|
@@ -739,8 +747,8 @@ class ObjectImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
739
747
|
template_name: The name of the template
|
|
740
748
|
"""
|
|
741
749
|
|
|
742
|
-
queryset
|
|
743
|
-
model_form
|
|
750
|
+
queryset: QuerySet
|
|
751
|
+
model_form: type[Form]
|
|
744
752
|
related_object_forms = {}
|
|
745
753
|
template_name = "generic/object_import.html"
|
|
746
754
|
|
|
@@ -884,8 +892,8 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): #
|
|
|
884
892
|
template_name: The name of the template
|
|
885
893
|
"""
|
|
886
894
|
|
|
887
|
-
queryset
|
|
888
|
-
table
|
|
895
|
+
queryset: QuerySet
|
|
896
|
+
table: type[Table]
|
|
889
897
|
template_name = "generic/object_bulk_import.html"
|
|
890
898
|
|
|
891
899
|
def __init__(self, *args, **kwargs):
|
|
@@ -990,10 +998,10 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDele
|
|
|
990
998
|
template_name: The name of the template
|
|
991
999
|
"""
|
|
992
1000
|
|
|
993
|
-
queryset
|
|
994
|
-
filterset = None
|
|
995
|
-
table
|
|
996
|
-
form
|
|
1001
|
+
queryset: QuerySet
|
|
1002
|
+
filterset: Optional[type[FilterSet]] = None
|
|
1003
|
+
table: type[Table]
|
|
1004
|
+
form: type[Form]
|
|
997
1005
|
template_name = "generic/object_bulk_edit.html"
|
|
998
1006
|
|
|
999
1007
|
def get_required_permission(self):
|
|
@@ -1164,7 +1172,7 @@ class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
1164
1172
|
An extendable view for renaming objects in bulk.
|
|
1165
1173
|
"""
|
|
1166
1174
|
|
|
1167
|
-
queryset
|
|
1175
|
+
queryset: QuerySet
|
|
1168
1176
|
template_name = "generic/object_bulk_rename.html"
|
|
1169
1177
|
|
|
1170
1178
|
def __init__(self, *args, **kwargs):
|
|
@@ -1270,10 +1278,10 @@ class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, EditAndDe
|
|
|
1270
1278
|
template_name: The name of the template
|
|
1271
1279
|
"""
|
|
1272
1280
|
|
|
1273
|
-
queryset
|
|
1274
|
-
filterset = None
|
|
1275
|
-
table
|
|
1276
|
-
form = None
|
|
1281
|
+
queryset: QuerySet
|
|
1282
|
+
filterset: Optional[type[FilterSet]] = None
|
|
1283
|
+
table: type[Table]
|
|
1284
|
+
form: Optional[type[Form]] = None
|
|
1277
1285
|
template_name = "generic/object_bulk_delete.html"
|
|
1278
1286
|
|
|
1279
1287
|
def get_required_permission(self):
|
|
@@ -1388,9 +1396,9 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
|
|
|
1388
1396
|
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
|
1389
1397
|
"""
|
|
1390
1398
|
|
|
1391
|
-
queryset
|
|
1392
|
-
form
|
|
1393
|
-
model_form
|
|
1399
|
+
queryset: QuerySet
|
|
1400
|
+
form: type[Form]
|
|
1401
|
+
model_form: type[Form]
|
|
1394
1402
|
template_name = "dcim/device_component_add.html"
|
|
1395
1403
|
|
|
1396
1404
|
def get_required_permission(self):
|
|
@@ -1490,13 +1498,13 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
|
|
|
1490
1498
|
Add one or more components (e.g. interfaces, console ports, etc.) to a set of Devices or VirtualMachines.
|
|
1491
1499
|
"""
|
|
1492
1500
|
|
|
1493
|
-
parent_model
|
|
1501
|
+
parent_model: type[Model]
|
|
1494
1502
|
parent_field = None
|
|
1495
|
-
form
|
|
1496
|
-
queryset
|
|
1497
|
-
model_form
|
|
1498
|
-
filterset = None
|
|
1499
|
-
table
|
|
1503
|
+
form: type[Form]
|
|
1504
|
+
queryset: QuerySet
|
|
1505
|
+
model_form: type[Form]
|
|
1506
|
+
filterset: Optional[type[FilterSet]] = None
|
|
1507
|
+
table: type[Table]
|
|
1500
1508
|
template_name = "generic/object_bulk_add_component.html"
|
|
1501
1509
|
|
|
1502
1510
|
def get_required_permission(self):
|
|
@@ -1510,7 +1518,7 @@ class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin,
|
|
|
1510
1518
|
|
|
1511
1519
|
# Are we editing *all* objects in the queryset or just a selected subset?
|
|
1512
1520
|
if request.POST.get("_all") and self.filterset is not None:
|
|
1513
|
-
pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only("pk")).qs]
|
|
1521
|
+
pk_list = [obj.pk for obj in self.filterset(request.GET, self.parent_model.objects.only("pk")).qs] # pylint: disable=not-callable
|
|
1514
1522
|
else:
|
|
1515
1523
|
pk_list = request.POST.getlist("pk")
|
|
1516
1524
|
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations # python 3.8
|
|
2
|
+
|
|
1
3
|
import logging
|
|
4
|
+
from typing import Optional
|
|
2
5
|
|
|
3
6
|
from django.contrib import messages
|
|
4
7
|
from django.contrib.auth.mixins import AccessMixin
|
|
@@ -22,6 +25,7 @@ from django.utils.encoding import iri_to_uri
|
|
|
22
25
|
from django.utils.html import format_html
|
|
23
26
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
24
27
|
from django.views.generic.edit import FormView
|
|
28
|
+
from django_filters import FilterSet
|
|
25
29
|
from drf_spectacular.utils import extend_schema
|
|
26
30
|
from rest_framework import exceptions, mixins
|
|
27
31
|
from rest_framework.decorators import action as drf_action
|
|
@@ -223,7 +227,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
223
227
|
# filterset and filter_params will be initialized in filter_queryset() in ObjectListViewMixin
|
|
224
228
|
filter_params = None
|
|
225
229
|
filterset = None
|
|
226
|
-
filterset_class = None
|
|
230
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
227
231
|
filterset_form_class = None
|
|
228
232
|
form_class = None
|
|
229
233
|
create_form_class = None
|
|
@@ -473,7 +477,11 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
473
477
|
def get_filter_params(self, request):
|
|
474
478
|
"""Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
|
|
475
479
|
params = request.GET.copy()
|
|
476
|
-
filter_params = get_filterable_params_from_filter_params(
|
|
480
|
+
filter_params = get_filterable_params_from_filter_params(
|
|
481
|
+
params,
|
|
482
|
+
self.non_filter_params,
|
|
483
|
+
self.filterset_class(), # pylint: disable=not-callable # only called if filterset_class is not None
|
|
484
|
+
)
|
|
477
485
|
if params.get("saved_view") and not filter_params and not params.get("all_filters_removed"):
|
|
478
486
|
return SavedView.objects.get(pk=params.get("saved_view")).config.get("filter_params", {})
|
|
479
487
|
return filter_params
|
|
@@ -628,8 +636,8 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
628
636
|
"""
|
|
629
637
|
|
|
630
638
|
action_buttons = ("add", "import", "export")
|
|
631
|
-
filterset_class = None
|
|
632
|
-
filterset_form_class = None
|
|
639
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
640
|
+
filterset_form_class: Optional[type[Form]] = None
|
|
633
641
|
hide_hierarchy_ui = False
|
|
634
642
|
non_filter_params = (
|
|
635
643
|
"export", # trigger for CSV/export-template/YAML export # 3.0 TODO: remove, irrelevant after #4746
|
|
@@ -780,7 +788,8 @@ class ObjectDestroyViewMixin(NautobotViewSetMixin, mixins.DestroyModelMixin):
|
|
|
780
788
|
return self.perform_destroy(request, **kwargs)
|
|
781
789
|
return Response(context)
|
|
782
790
|
|
|
783
|
-
|
|
791
|
+
# TODO: this conflicts with DRF's DestroyModelMixin.perform_destroy(self, instance) API
|
|
792
|
+
def perform_destroy(self, request, **kwargs): # pylint:disable=arguments-renamed
|
|
784
793
|
"""
|
|
785
794
|
Function to validate the ObjectDeleteConfirmationForm and to delete the object.
|
|
786
795
|
"""
|
|
@@ -934,7 +943,7 @@ class EditAndDeleteAllModelMixin:
|
|
|
934
943
|
filterset_class = getattr(self, "filterset_class", None)
|
|
935
944
|
|
|
936
945
|
if request.GET and filterset_class is not None:
|
|
937
|
-
queryset = filterset_class(request.GET, model.objects.all()).qs
|
|
946
|
+
queryset = filterset_class(request.GET, model.objects.all()).qs # pylint: disable=not-callable
|
|
938
947
|
# We take this approach because filterset.qs has already applied .distinct(),
|
|
939
948
|
# and performing a .delete directly on a queryset with .distinct applied is not allowed.
|
|
940
949
|
queryset = self.queryset.filter(pk__in=queryset)
|
|
@@ -958,8 +967,8 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
|
|
|
958
967
|
UI mixin to bulk destroy model instances.
|
|
959
968
|
"""
|
|
960
969
|
|
|
961
|
-
bulk_destroy_form_class = None
|
|
962
|
-
filterset_class = None
|
|
970
|
+
bulk_destroy_form_class: Optional[type[Form]] = None
|
|
971
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
963
972
|
|
|
964
973
|
def _process_bulk_destroy_form(self, form):
|
|
965
974
|
request = self.request
|
|
@@ -992,7 +1001,8 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
|
|
|
992
1001
|
"""
|
|
993
1002
|
return self.perform_bulk_destroy(request, **kwargs)
|
|
994
1003
|
|
|
995
|
-
|
|
1004
|
+
# TODO: this conflicts with BulkDestroyModelMixin.perform_bulk_destroy(self, objects)
|
|
1005
|
+
def perform_bulk_destroy(self, request, **kwargs): # pylint:disable=arguments-renamed
|
|
996
1006
|
"""
|
|
997
1007
|
request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer.
|
|
998
1008
|
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.
|
|
@@ -1087,8 +1097,8 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
|
|
|
1087
1097
|
UI mixin to bulk update model instances.
|
|
1088
1098
|
"""
|
|
1089
1099
|
|
|
1090
|
-
filterset_class = None
|
|
1091
|
-
bulk_update_form_class = None
|
|
1100
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
1101
|
+
bulk_update_form_class: Optional[type[Form]] = None
|
|
1092
1102
|
|
|
1093
1103
|
def _process_bulk_update_form(self, form):
|
|
1094
1104
|
request = self.request
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -274,15 +274,15 @@ class LocationSerializer(
|
|
|
274
274
|
],
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
def validate(self,
|
|
277
|
+
def validate(self, attrs):
|
|
278
278
|
# Validate uniqueness of (parent, name) since we omitted the automatically created validator from Meta.
|
|
279
|
-
if
|
|
279
|
+
if attrs.get("parent") and attrs.get("name"):
|
|
280
280
|
validator = UniqueTogetherValidator(queryset=Location.objects.all(), fields=("parent", "name"))
|
|
281
|
-
validator(
|
|
281
|
+
validator(attrs, self)
|
|
282
282
|
|
|
283
|
-
super().validate(
|
|
283
|
+
super().validate(attrs)
|
|
284
284
|
|
|
285
|
-
return
|
|
285
|
+
return attrs
|
|
286
286
|
|
|
287
287
|
|
|
288
288
|
#
|
|
@@ -328,20 +328,20 @@ class RackSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
328
328
|
"include_others": True,
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
def validate(self,
|
|
331
|
+
def validate(self, attrs):
|
|
332
332
|
# Validate uniqueness of (rack_group, name) since we omitted the automatically-created validator above.
|
|
333
|
-
if
|
|
333
|
+
if attrs.get("rack_group", None):
|
|
334
334
|
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "name"))
|
|
335
|
-
validator(
|
|
335
|
+
validator(attrs, self)
|
|
336
336
|
# Validate uniqueness of (rack_group, facility_id) since we omitted the automatically-created validator above.
|
|
337
|
-
if
|
|
337
|
+
if attrs.get("facility_id", None) and attrs.get("rack_group", None):
|
|
338
338
|
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "facility_id"))
|
|
339
|
-
validator(
|
|
339
|
+
validator(attrs, self)
|
|
340
340
|
|
|
341
341
|
# Enforce model validation
|
|
342
|
-
super().validate(
|
|
342
|
+
super().validate(attrs)
|
|
343
343
|
|
|
344
|
-
return
|
|
344
|
+
return attrs
|
|
345
345
|
|
|
346
346
|
|
|
347
347
|
class RackUnitSerializer(serializers.Serializer):
|
|
@@ -673,20 +673,20 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
673
673
|
def get_config_context(self, obj):
|
|
674
674
|
return obj.get_config_context()
|
|
675
675
|
|
|
676
|
-
def validate(self,
|
|
676
|
+
def validate(self, attrs):
|
|
677
677
|
# Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
|
|
678
|
-
if
|
|
678
|
+
if attrs.get("rack") and attrs.get("position") and attrs.get("face"):
|
|
679
679
|
validator = UniqueTogetherValidator(
|
|
680
680
|
queryset=Device.objects.all(),
|
|
681
681
|
fields=("rack", "position", "face"),
|
|
682
682
|
message=f"The position and face is already occupied on this rack. {UniqueTogetherValidator.message}",
|
|
683
683
|
)
|
|
684
|
-
validator(
|
|
684
|
+
validator(attrs, self)
|
|
685
685
|
|
|
686
686
|
# Enforce model validation
|
|
687
|
-
super().validate(
|
|
687
|
+
super().validate(attrs)
|
|
688
688
|
|
|
689
|
-
return
|
|
689
|
+
return attrs
|
|
690
690
|
|
|
691
691
|
|
|
692
692
|
class DeviceNAPALMSerializer(serializers.Serializer):
|
|
@@ -759,22 +759,22 @@ class PowerPortSerializer(
|
|
|
759
759
|
|
|
760
760
|
|
|
761
761
|
class InterfaceCommonSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
762
|
-
def validate(self,
|
|
762
|
+
def validate(self, attrs):
|
|
763
763
|
# Validate many-to-many VLAN assignments
|
|
764
|
-
mode =
|
|
764
|
+
mode = attrs.get("mode", getattr(self.instance, "mode", None))
|
|
765
765
|
|
|
766
766
|
if mode != InterfaceModeChoices.MODE_TAGGED:
|
|
767
|
-
if
|
|
767
|
+
if attrs.get("tagged_vlans"):
|
|
768
768
|
raise serializers.ValidationError(
|
|
769
769
|
{
|
|
770
770
|
"tagged_vlans": f"Mode must be set to {InterfaceModeChoices.MODE_TAGGED} when specifying tagged_vlans"
|
|
771
771
|
}
|
|
772
772
|
)
|
|
773
773
|
|
|
774
|
-
if
|
|
774
|
+
if attrs.get("tagged_vlans") != [] and self.instance and self.instance.tagged_vlans.exists():
|
|
775
775
|
raise serializers.ValidationError({"tagged_vlans": f"Clear tagged_vlans to set mode to {mode}"})
|
|
776
776
|
|
|
777
|
-
return super().validate(
|
|
777
|
+
return super().validate(attrs)
|
|
778
778
|
|
|
779
779
|
|
|
780
780
|
class InterfaceSerializer(
|
|
@@ -877,18 +877,18 @@ class InventoryItemSerializer(TaggedModelSerializerMixin, TreeModelSerializerMix
|
|
|
877
877
|
# https://www.django-rest-framework.org/api-guide/validators/#optional-fields
|
|
878
878
|
validators = []
|
|
879
879
|
|
|
880
|
-
def validate(self,
|
|
880
|
+
def validate(self, attrs):
|
|
881
881
|
# Validate uniqueness of (device, parent, name) since we omitted the automatically created validator from Meta.
|
|
882
|
-
if
|
|
882
|
+
if attrs.get("device") and attrs.get("parent") and attrs.get("name"):
|
|
883
883
|
validator = UniqueTogetherValidator(
|
|
884
884
|
queryset=InventoryItem.objects.all(),
|
|
885
885
|
fields=("device", "parent", "name"),
|
|
886
886
|
)
|
|
887
|
-
validator(
|
|
887
|
+
validator(attrs, self)
|
|
888
888
|
|
|
889
|
-
super().validate(
|
|
889
|
+
super().validate(attrs)
|
|
890
890
|
|
|
891
|
-
return
|
|
891
|
+
return attrs
|
|
892
892
|
|
|
893
893
|
|
|
894
894
|
#
|
|
@@ -1135,21 +1135,21 @@ class ModuleBaySerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
1135
1135
|
fields = "__all__"
|
|
1136
1136
|
validators = []
|
|
1137
1137
|
|
|
1138
|
-
def validate(self,
|
|
1138
|
+
def validate(self, attrs):
|
|
1139
1139
|
"""Validate device and module field constraints for module bay."""
|
|
1140
|
-
if
|
|
1140
|
+
if attrs.get("parent_device") and attrs.get("parent_module"):
|
|
1141
1141
|
raise serializers.ValidationError("Only one of parent_device or parent_module must be set")
|
|
1142
|
-
if
|
|
1142
|
+
if attrs.get("parent_device"):
|
|
1143
1143
|
validator = UniqueTogetherValidator(
|
|
1144
1144
|
queryset=self.Meta.model.objects.all(), fields=("parent_device", "name")
|
|
1145
1145
|
)
|
|
1146
|
-
validator(
|
|
1147
|
-
if
|
|
1146
|
+
validator(attrs, self)
|
|
1147
|
+
if attrs.get("parent_module"):
|
|
1148
1148
|
validator = UniqueTogetherValidator(
|
|
1149
1149
|
queryset=self.Meta.model.objects.all(), fields=("parent_module", "name")
|
|
1150
1150
|
)
|
|
1151
|
-
validator(
|
|
1152
|
-
return super().validate(
|
|
1151
|
+
validator(attrs, self)
|
|
1152
|
+
return super().validate(attrs)
|
|
1153
1153
|
|
|
1154
1154
|
|
|
1155
1155
|
class ModuleBayTemplateSerializer(NautobotModelSerializer):
|
|
@@ -1158,17 +1158,17 @@ class ModuleBayTemplateSerializer(NautobotModelSerializer):
|
|
|
1158
1158
|
fields = "__all__"
|
|
1159
1159
|
validators = []
|
|
1160
1160
|
|
|
1161
|
-
def validate(self,
|
|
1161
|
+
def validate(self, attrs):
|
|
1162
1162
|
"""Validate device_type and module_type field constraints for module bay template."""
|
|
1163
|
-
if
|
|
1163
|
+
if attrs.get("device_type") and attrs.get("module_type"):
|
|
1164
1164
|
raise serializers.ValidationError("Only one of device_type or module_type must be set")
|
|
1165
|
-
if
|
|
1165
|
+
if attrs.get("device_type"):
|
|
1166
1166
|
validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("device_type", "name"))
|
|
1167
|
-
validator(
|
|
1168
|
-
if
|
|
1167
|
+
validator(attrs, self)
|
|
1168
|
+
if attrs.get("module_type"):
|
|
1169
1169
|
validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("module_type", "name"))
|
|
1170
|
-
validator(
|
|
1171
|
-
return super().validate(
|
|
1170
|
+
validator(attrs, self)
|
|
1171
|
+
return super().validate(attrs)
|
|
1172
1172
|
|
|
1173
1173
|
|
|
1174
1174
|
class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
@@ -1178,17 +1178,17 @@ class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
1178
1178
|
|
|
1179
1179
|
validators = []
|
|
1180
1180
|
|
|
1181
|
-
def validate(self,
|
|
1181
|
+
def validate(self, attrs):
|
|
1182
1182
|
"""Validate asset_Tag, serial, parent_module_bay and location field constraints for module."""
|
|
1183
|
-
if
|
|
1183
|
+
if attrs.get("parent_module_bay") and attrs.get("location"):
|
|
1184
1184
|
raise serializers.ValidationError("Only one of parent_module_bay or location must be set")
|
|
1185
|
-
if
|
|
1185
|
+
if attrs.get("serial"):
|
|
1186
1186
|
validator = UniqueTogetherValidator(queryset=Module.objects.all(), fields=("module_type", "serial"))
|
|
1187
|
-
validator(
|
|
1188
|
-
if
|
|
1187
|
+
validator(attrs, self)
|
|
1188
|
+
if attrs.get("asset_tag"):
|
|
1189
1189
|
validator = UniqueValidator(queryset=Module.objects.all())
|
|
1190
|
-
validator(
|
|
1191
|
-
return super().validate(
|
|
1190
|
+
validator(attrs["asset_tag"], self.fields["asset_tag"])
|
|
1191
|
+
return super().validate(attrs)
|
|
1192
1192
|
|
|
1193
1193
|
|
|
1194
1194
|
class ModuleTypeSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
nautobot/dcim/forms.py
CHANGED
|
@@ -1651,6 +1651,8 @@ class ComponentTemplateImportForm(BootstrapMixin, CustomFieldModelCSVForm):
|
|
|
1651
1651
|
netbox-community/devicetype-library repository.
|
|
1652
1652
|
"""
|
|
1653
1653
|
|
|
1654
|
+
Meta: type # to be defined by concrete subclasses
|
|
1655
|
+
|
|
1654
1656
|
def __init__(self, data=None, *args, **kwargs):
|
|
1655
1657
|
super().__init__(data, *args, **kwargs)
|
|
1656
1658
|
|
nautobot/dcim/graphql/types.py
CHANGED
|
@@ -98,13 +98,13 @@ class CableType(OptimizedNautobotObjectType):
|
|
|
98
98
|
|
|
99
99
|
def resolve_termination_a_type(self, args):
|
|
100
100
|
if self.termination_a_type:
|
|
101
|
-
model = self.termination_a_type.model_class()
|
|
101
|
+
model = self.termination_a_type.model_class() # pylint: disable=no-member
|
|
102
102
|
return f"{model._meta.app_label}.{model._meta.model_name}"
|
|
103
103
|
return None
|
|
104
104
|
|
|
105
105
|
def resolve_termination_b_type(self, args):
|
|
106
106
|
if self.termination_b_type:
|
|
107
|
-
model = self.termination_b_type.model_class()
|
|
107
|
+
model = self.termination_b_type.model_class() # pylint: disable=no-member
|
|
108
108
|
return f"{model._meta.app_label}.{model._meta.model_name}"
|
|
109
109
|
return None
|
|
110
110
|
|
|
@@ -100,7 +100,7 @@ class ComponentTemplateModel(
|
|
|
100
100
|
def get_absolute_url(self, api=False):
|
|
101
101
|
# TODO: in the new UI, this should be able to link directly to the object, instead of the device-type.
|
|
102
102
|
if not api:
|
|
103
|
-
return self.device_type.get_absolute_url(api=api)
|
|
103
|
+
return self.device_type.get_absolute_url(api=api) # pylint: disable=no-member
|
|
104
104
|
return super().get_absolute_url(api=api)
|
|
105
105
|
|
|
106
106
|
def instantiate_model(self, model, device, **kwargs):
|
|
@@ -460,7 +460,7 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|
|
460
460
|
return self.instantiate_model(model=DeviceBay, device=device)
|
|
461
461
|
|
|
462
462
|
def clean(self):
|
|
463
|
-
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT:
|
|
463
|
+
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT: # pylint: disable=no-member
|
|
464
464
|
raise ValidationError(
|
|
465
465
|
f'Subdevice role of device type ({self.device_type}) must be set to "parent" to allow device bays.'
|
|
466
466
|
)
|