nautobot 2.4.0b1__py3-none-any.whl → 2.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/__init__.py +1 -1
- nautobot/apps/api.py +8 -8
- nautobot/apps/change_logging.py +2 -2
- nautobot/apps/choices.py +4 -4
- nautobot/apps/events.py +3 -3
- nautobot/apps/factory.py +2 -2
- nautobot/apps/filters.py +1 -1
- nautobot/apps/forms.py +20 -20
- nautobot/apps/graphql.py +2 -2
- nautobot/apps/jobs.py +8 -8
- nautobot/apps/models.py +19 -19
- nautobot/apps/tables.py +1 -1
- nautobot/apps/testing.py +10 -10
- nautobot/apps/ui.py +2 -2
- nautobot/apps/utils.py +7 -7
- nautobot/apps/views.py +7 -7
- nautobot/circuits/api/serializers.py +1 -0
- nautobot/circuits/api/views.py +4 -8
- nautobot/circuits/tables.py +2 -1
- nautobot/circuits/templates/circuits/circuit_create.html +1 -7
- nautobot/circuits/views.py +3 -3
- nautobot/cloud/api/views.py +6 -10
- nautobot/cloud/models.py +1 -1
- nautobot/cloud/views.py +0 -16
- nautobot/core/api/constants.py +11 -0
- nautobot/core/api/fields.py +5 -5
- nautobot/core/api/filter_backends.py +3 -9
- nautobot/core/api/schema.py +13 -2
- nautobot/core/api/serializers.py +40 -34
- nautobot/core/api/views.py +56 -4
- nautobot/core/celery/log.py +4 -4
- nautobot/core/celery/schedulers.py +2 -2
- nautobot/core/choices.py +2 -2
- nautobot/core/events/__init__.py +3 -3
- nautobot/core/filters.py +67 -35
- nautobot/core/forms/__init__.py +19 -19
- nautobot/core/forms/fields.py +14 -11
- nautobot/core/forms/forms.py +33 -2
- nautobot/core/graphql/types.py +1 -1
- nautobot/core/jobs/__init__.py +28 -7
- nautobot/core/jobs/bulk_actions.py +285 -0
- nautobot/core/jobs/cleanup.py +48 -12
- nautobot/core/jobs/groups.py +1 -1
- nautobot/core/management/commands/validate_models.py +1 -1
- nautobot/core/models/__init__.py +3 -1
- nautobot/core/models/query_functions.py +2 -2
- nautobot/core/models/tree_queries.py +6 -3
- nautobot/core/settings.py +29 -2
- nautobot/core/settings.yaml +21 -0
- nautobot/core/tables.py +79 -61
- nautobot/core/templates/about.html +67 -0
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
- nautobot/core/templates/inc/tenant_table_row.html +11 -0
- nautobot/core/templates/nautobot_config.py.j2 +13 -0
- nautobot/core/templates/search.html +7 -0
- nautobot/core/templates/utilities/render_jinja2.html +1 -1
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/theme_preview.html +7 -0
- nautobot/core/templatetags/helpers.py +11 -2
- nautobot/core/testing/__init__.py +8 -8
- nautobot/core/testing/api.py +170 -15
- nautobot/core/testing/filters.py +45 -10
- nautobot/core/testing/forms.py +2 -0
- nautobot/core/testing/integration.py +86 -4
- nautobot/core/testing/mixins.py +7 -2
- nautobot/core/testing/views.py +44 -29
- 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_api.py +98 -1
- nautobot/core/tests/test_csv.py +25 -3
- nautobot/core/tests/test_filters.py +209 -246
- nautobot/core/tests/test_forms.py +1 -0
- nautobot/core/tests/test_jobs.py +460 -1
- nautobot/core/tests/test_models.py +9 -0
- nautobot/core/tests/test_settings_schema.py +7 -0
- nautobot/core/tests/test_tables.py +100 -0
- nautobot/core/tests/test_utils.py +63 -1
- nautobot/core/tests/test_views.py +30 -3
- nautobot/core/ui/nav.py +1 -0
- nautobot/core/ui/object_detail.py +15 -1
- nautobot/core/urls.py +11 -0
- nautobot/core/utils/lookup.py +11 -8
- nautobot/core/utils/querysets.py +64 -0
- nautobot/core/utils/requests.py +24 -9
- nautobot/core/views/__init__.py +42 -0
- nautobot/core/views/generic.py +131 -197
- nautobot/core/views/mixins.py +126 -38
- nautobot/core/views/renderers.py +6 -6
- nautobot/dcim/api/serializers.py +56 -64
- nautobot/dcim/api/views.py +47 -113
- nautobot/dcim/constants.py +6 -13
- nautobot/dcim/factory.py +6 -1
- nautobot/dcim/filters/__init__.py +31 -2
- nautobot/dcim/forms.py +36 -17
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
- nautobot/dcim/models/__init__.py +1 -1
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +22 -20
- nautobot/dcim/models/devices.py +10 -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/devices.py +7 -5
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/tables/racks.py +1 -1
- nautobot/dcim/templates/dcim/controller_create.html +1 -7
- nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
- nautobot/dcim/templates/dcim/device.html +1 -9
- nautobot/dcim/templates/dcim/device_edit.html +36 -37
- nautobot/dcim/templates/dcim/location.html +1 -9
- nautobot/dcim/templates/dcim/location_edit.html +1 -7
- nautobot/dcim/templates/dcim/rack.html +1 -9
- nautobot/dcim/templates/dcim/rack_edit.html +1 -7
- nautobot/dcim/templates/dcim/rackreservation.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
- nautobot/dcim/tests/integration/test_controller.py +62 -0
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +189 -0
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +181 -0
- nautobot/dcim/tests/test_api.py +16 -5
- nautobot/dcim/tests/test_filters.py +33 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/dcim/tests/test_graphql.py +52 -0
- nautobot/dcim/tests/test_jobs.py +118 -0
- nautobot/dcim/tests/test_models.py +52 -9
- nautobot/dcim/tests/test_views.py +21 -83
- nautobot/dcim/views.py +1 -13
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +90 -85
- nautobot/extras/api/views.py +22 -27
- nautobot/extras/constants.py +2 -0
- nautobot/extras/filters/__init__.py +8 -6
- nautobot/extras/forms/base.py +2 -2
- nautobot/extras/forms/forms.py +139 -31
- nautobot/extras/forms/mixins.py +14 -6
- nautobot/extras/group_sync.py +3 -3
- nautobot/extras/health_checks.py +1 -2
- nautobot/extras/jobs.py +85 -18
- nautobot/extras/managers.py +3 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
- nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
- nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
- nautobot/extras/models/__init__.py +1 -1
- nautobot/extras/models/contacts.py +1 -1
- nautobot/extras/models/customfields.py +12 -11
- nautobot/extras/models/groups.py +11 -9
- nautobot/extras/models/jobs.py +23 -4
- nautobot/extras/models/models.py +2 -2
- nautobot/extras/plugins/__init__.py +13 -2
- nautobot/extras/plugins/marketplace_manifest.yml +84 -79
- nautobot/extras/plugins/tables.py +16 -14
- nautobot/extras/plugins/views.py +65 -69
- nautobot/extras/registry.py +1 -1
- nautobot/extras/secrets/__init__.py +2 -2
- nautobot/extras/tables.py +7 -5
- nautobot/extras/templates/extras/dynamicgroup.html +1 -9
- nautobot/extras/templates/extras/job_detail.html +16 -0
- nautobot/extras/templates/extras/job_edit.html +1 -0
- nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
- nautobot/extras/templates/extras/marketplace.html +29 -11
- nautobot/extras/templates/extras/plugin_detail.html +32 -15
- nautobot/extras/templates/extras/plugins_tiles.html +21 -10
- nautobot/extras/templatetags/job_buttons.py +4 -4
- 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/singleton.py +16 -0
- 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 +33 -1
- nautobot/extras/tests/test_jobs.py +178 -32
- nautobot/extras/tests/test_models.py +16 -10
- nautobot/extras/tests/test_plugins.py +62 -9
- nautobot/extras/tests/test_relationships.py +120 -9
- nautobot/extras/tests/test_views.py +56 -194
- nautobot/extras/utils.py +3 -2
- nautobot/extras/views.py +30 -98
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +41 -33
- nautobot/ipam/api/views.py +68 -117
- nautobot/ipam/factory.py +1 -1
- nautobot/ipam/filters.py +3 -2
- nautobot/ipam/lookups.py +101 -62
- nautobot/ipam/models.py +66 -16
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tables.py +23 -7
- nautobot/ipam/templates/ipam/ipaddress.html +1 -9
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
- nautobot/ipam/templates/ipam/prefix.html +1 -9
- nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
- nautobot/ipam/templates/ipam/vlan.html +1 -9
- nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
- nautobot/ipam/tests/test_api.py +436 -3
- nautobot/ipam/tests/test_forms.py +49 -47
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_models.py +95 -34
- nautobot/ipam/tests/test_querysets.py +63 -1
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/ipam/utils/__init__.py +36 -6
- nautobot/ipam/views.py +61 -87
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
- nautobot/project-static/docs/404.html +46 -4
- nautobot/project-static/docs/apps/index.html +46 -4
- nautobot/project-static/docs/apps/nautobot-apps.html +47 -6
- nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
- 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 +62 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +59 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +374 -122
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +90 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +95 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +53 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +52 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +79 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +102 -28
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +108 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +131 -38
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +239 -65
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +581 -165
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +109 -36
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +453 -167
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +493 -211
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +71 -15
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +407 -55
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +620 -205
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +858 -412
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +59 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +448 -186
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +365 -147
- nautobot/project-static/docs/development/apps/api/configuration-view.html +46 -4
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/global-search.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/graphql.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +68 -7
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +46 -4
- nautobot/project-static/docs/development/apps/api/prometheus.html +46 -4
- nautobot/project-static/docs/development/apps/api/setup.html +46 -4
- nautobot/project-static/docs/development/apps/api/testing.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/base-template.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +52 -6
- nautobot/project-static/docs/development/apps/api/views/urls.html +46 -4
- nautobot/project-static/docs/development/apps/index.html +46 -4
- nautobot/project-static/docs/development/apps/migration/code-updates.html +46 -4
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +46 -4
- nautobot/project-static/docs/development/apps/migration/from-v1.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +46 -4
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +50 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +46 -4
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +211 -14
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +46 -4
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +46 -4
- nautobot/project-static/docs/development/core/application-registry.html +46 -4
- nautobot/project-static/docs/development/core/best-practices.html +46 -4
- nautobot/project-static/docs/development/core/bootstrap-ui.html +46 -4
- nautobot/project-static/docs/development/core/caching.html +46 -4
- nautobot/project-static/docs/development/core/controllers.html +46 -4
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +73 -74
- nautobot/project-static/docs/development/core/generic-views.html +46 -4
- nautobot/project-static/docs/development/core/getting-started.html +249 -224
- nautobot/project-static/docs/development/core/homepage.html +49 -7
- nautobot/project-static/docs/development/core/index.html +46 -4
- nautobot/project-static/docs/development/core/{local-k8s.html → minikube-dev-environment-for-k8s-jobs.html} +469 -168
- nautobot/project-static/docs/development/core/model-checklist.html +56 -12
- nautobot/project-static/docs/development/core/model-features.html +46 -4
- nautobot/project-static/docs/development/core/natural-keys.html +46 -4
- nautobot/project-static/docs/development/core/navigation-menu.html +46 -4
- nautobot/project-static/docs/development/core/release-checklist.html +49 -7
- nautobot/project-static/docs/development/core/role-internals.html +46 -4
- nautobot/project-static/docs/development/core/settings.html +46 -4
- nautobot/project-static/docs/development/core/style-guide.html +49 -7
- nautobot/project-static/docs/development/core/templates.html +46 -4
- nautobot/project-static/docs/development/core/testing.html +46 -4
- nautobot/project-static/docs/development/core/ui-component-framework.html +369 -273
- nautobot/project-static/docs/development/core/user-preferences.html +46 -4
- nautobot/project-static/docs/development/index.html +46 -4
- nautobot/project-static/docs/development/jobs/index.html +216 -122
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +46 -4
- nautobot/project-static/docs/index.html +54 -23
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +47 -7
- nautobot/project-static/docs/overview/design_philosophy.html +46 -4
- nautobot/project-static/docs/release-notes/index.html +52 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +234 -193
- nautobot/project-static/docs/release-notes/version-1.1.html +231 -190
- nautobot/project-static/docs/release-notes/version-1.2.html +306 -265
- nautobot/project-static/docs/release-notes/version-1.3.html +332 -291
- nautobot/project-static/docs/release-notes/version-1.4.html +417 -377
- nautobot/project-static/docs/release-notes/version-1.5.html +605 -566
- nautobot/project-static/docs/release-notes/version-1.6.html +904 -447
- nautobot/project-static/docs/release-notes/version-2.0.html +528 -489
- nautobot/project-static/docs/release-notes/version-2.1.html +363 -324
- nautobot/project-static/docs/release-notes/version-2.2.html +356 -317
- nautobot/project-static/docs/release-notes/version-2.3.html +997 -352
- nautobot/project-static/docs/release-notes/version-2.4.html +525 -101
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +295 -287
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +48 -6
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +110 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -6
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +66 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +47 -5
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +46 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +46 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +46 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +46 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +49 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +50 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +49 -7
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +51 -7
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +49 -7
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
- nautobot/project-static/docs/user-guide/index.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +50 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +50 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +49 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +47 -5
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +94 -25
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +74 -5
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +46 -4
- nautobot/project-static/js/forms.js +1 -1
- nautobot/tenancy/api/views.py +9 -13
- nautobot/tenancy/views.py +4 -2
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +5 -4
- nautobot/users/api/views.py +3 -3
- nautobot/virtualization/api/serializers.py +4 -4
- nautobot/virtualization/api/views.py +5 -24
- nautobot/virtualization/filters.py +20 -3
- nautobot/virtualization/models.py +1 -1
- nautobot/virtualization/tables.py +2 -2
- nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
- nautobot/virtualization/tests/test_filters.py +17 -0
- nautobot/wireless/filters.py +2 -2
- nautobot/wireless/forms.py +1 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
- nautobot/wireless/tests/integration/__init__.py +0 -0
- nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
- nautobot/wireless/tests/test_filters.py +29 -1
- nautobot/wireless/tests/test_views.py +22 -1
- nautobot/wireless/views.py +0 -10
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/METADATA +6 -6
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/RECORD +600 -550
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/WHEEL +1 -1
- nautobot/core/fixtures/user-data.json +0 -59
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/NOTICE +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/entry_points.txt +0 -0
nautobot/core/views/mixins.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import ClassVar, Optional
|
|
2
3
|
|
|
3
4
|
from django.contrib import messages
|
|
4
5
|
from django.contrib.auth.mixins import AccessMixin
|
|
@@ -22,6 +23,7 @@ from django.utils.encoding import iri_to_uri
|
|
|
22
23
|
from django.utils.html import format_html
|
|
23
24
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
24
25
|
from django.views.generic.edit import FormView
|
|
26
|
+
from django_filters import FilterSet
|
|
25
27
|
from drf_spectacular.utils import extend_schema
|
|
26
28
|
from rest_framework import exceptions, mixins
|
|
27
29
|
from rest_framework.decorators import action as drf_action
|
|
@@ -29,6 +31,7 @@ from rest_framework.parsers import FormParser, MultiPartParser
|
|
|
29
31
|
from rest_framework.response import Response
|
|
30
32
|
from rest_framework.viewsets import GenericViewSet
|
|
31
33
|
|
|
34
|
+
from nautobot.core import exceptions as core_exceptions
|
|
32
35
|
from nautobot.core.api.views import BulkDestroyModelMixin, BulkUpdateModelMixin
|
|
33
36
|
from nautobot.core.forms import (
|
|
34
37
|
BootstrapMixin,
|
|
@@ -37,7 +40,8 @@ from nautobot.core.forms import (
|
|
|
37
40
|
CSVFileField,
|
|
38
41
|
restrict_form_fields,
|
|
39
42
|
)
|
|
40
|
-
from nautobot.core.
|
|
43
|
+
from nautobot.core.jobs import BulkDeleteObjects, BulkEditObjects
|
|
44
|
+
from nautobot.core.utils import filtering, lookup, permissions
|
|
41
45
|
from nautobot.core.utils.requests import get_filterable_params_from_filter_params, normalize_querydict
|
|
42
46
|
from nautobot.core.views.renderers import NautobotHTMLRenderer
|
|
43
47
|
from nautobot.core.views.utils import (
|
|
@@ -48,7 +52,7 @@ from nautobot.core.views.utils import (
|
|
|
48
52
|
)
|
|
49
53
|
from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
|
|
50
54
|
from nautobot.extras.forms import NoteForm
|
|
51
|
-
from nautobot.extras.models import ExportTemplate, SavedView, UserSavedViewAssociation
|
|
55
|
+
from nautobot.extras.models import ExportTemplate, Job, JobResult, SavedView, UserSavedViewAssociation
|
|
52
56
|
from nautobot.extras.tables import NoteTable, ObjectChangeTable
|
|
53
57
|
from nautobot.extras.utils import bulk_delete_with_bulk_change_logging, get_base_template, remove_prefix_from_cf_key
|
|
54
58
|
|
|
@@ -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
|
|
@@ -579,11 +587,11 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
579
587
|
if self.action == "bulk_destroy":
|
|
580
588
|
queryset = self.get_queryset()
|
|
581
589
|
bulk_delete_all = bool(self.request.POST.get("_all"))
|
|
582
|
-
if bulk_delete_all:
|
|
583
|
-
return ConfirmationForm
|
|
584
590
|
|
|
585
591
|
class BulkDestroyForm(ConfirmationForm):
|
|
586
|
-
pk = ModelMultipleChoiceField(
|
|
592
|
+
pk = ModelMultipleChoiceField(
|
|
593
|
+
queryset=queryset, widget=MultipleHiddenInput, required=not bulk_delete_all
|
|
594
|
+
)
|
|
587
595
|
|
|
588
596
|
return BulkDestroyForm
|
|
589
597
|
else:
|
|
@@ -629,8 +637,8 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
629
637
|
"""
|
|
630
638
|
|
|
631
639
|
action_buttons = ("add", "import", "export")
|
|
632
|
-
filterset_class = None
|
|
633
|
-
filterset_form_class = None
|
|
640
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
641
|
+
filterset_form_class: Optional[type[Form]] = None
|
|
634
642
|
hide_hierarchy_ui = False
|
|
635
643
|
non_filter_params = (
|
|
636
644
|
"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
|
"""
|
|
@@ -912,11 +921,13 @@ class ObjectEditViewMixin(NautobotViewSetMixin, mixins.CreateModelMixin, mixins.
|
|
|
912
921
|
return self.form_invalid(form)
|
|
913
922
|
|
|
914
923
|
|
|
915
|
-
class
|
|
924
|
+
class BulkEditAndBulkDeleteModelMixin:
|
|
916
925
|
"""
|
|
917
|
-
UI mixin to bulk destroy
|
|
926
|
+
UI mixin to bulk destroy and bulk edit all model instances.
|
|
918
927
|
"""
|
|
919
928
|
|
|
929
|
+
logger = logging.getLogger(__name__)
|
|
930
|
+
|
|
920
931
|
def _get_bulk_edit_delete_all_queryset(self, request):
|
|
921
932
|
"""
|
|
922
933
|
Retrieve the queryset of model instances to be bulk-deleted or bulk-deleted, filtered based on request parameters.
|
|
@@ -934,7 +945,7 @@ class EditAndDeleteAllModelMixin:
|
|
|
934
945
|
filterset_class = getattr(self, "filterset_class", None)
|
|
935
946
|
|
|
936
947
|
if request.GET and filterset_class is not None:
|
|
937
|
-
queryset = filterset_class(request.GET, model.objects.all()).qs
|
|
948
|
+
queryset = filterset_class(request.GET, model.objects.all()).qs # pylint: disable=not-callable
|
|
938
949
|
# We take this approach because filterset.qs has already applied .distinct(),
|
|
939
950
|
# and performing a .delete directly on a queryset with .distinct applied is not allowed.
|
|
940
951
|
queryset = self.queryset.filter(pk__in=queryset)
|
|
@@ -942,24 +953,92 @@ class EditAndDeleteAllModelMixin:
|
|
|
942
953
|
queryset = model.objects.all()
|
|
943
954
|
return queryset
|
|
944
955
|
|
|
945
|
-
def
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
"obj_type_plural": model._meta.verbose_name_plural,
|
|
949
|
-
"return_url": self.get_return_url(request),
|
|
950
|
-
"total_objs_to_delete": queryset.count(),
|
|
951
|
-
"delete_all": True,
|
|
952
|
-
"table": None,
|
|
953
|
-
}
|
|
956
|
+
def send_bulk_delete_objects_to_job(self, request, pk_list, model, delete_all):
|
|
957
|
+
"""Prepare and enqueue bulk delete job."""
|
|
958
|
+
job_model = Job.objects.get_for_class_path(BulkDeleteObjects.class_path)
|
|
954
959
|
|
|
960
|
+
if filterset_class := lookup.get_filterset_for_model(model):
|
|
961
|
+
filter_query_params = normalize_querydict(request.GET, filterset=filterset_class())
|
|
962
|
+
else:
|
|
963
|
+
filter_query_params = {}
|
|
964
|
+
|
|
965
|
+
# Discarding non-filter query params
|
|
966
|
+
new_filter_query_params = {}
|
|
967
|
+
|
|
968
|
+
for key, value in filter_query_params.items():
|
|
969
|
+
try:
|
|
970
|
+
filtering.get_filterset_field(filterset_class(), key)
|
|
971
|
+
new_filter_query_params[key] = value
|
|
972
|
+
except core_exceptions.FilterSetFieldNotFound:
|
|
973
|
+
self.logger.debug(f"Query parameter `{key}` not found in `{filterset_class}`, discarding it")
|
|
974
|
+
|
|
975
|
+
filter_query_params = new_filter_query_params
|
|
976
|
+
|
|
977
|
+
job_form = BulkDeleteObjects.as_form(
|
|
978
|
+
data={
|
|
979
|
+
"pk_list": pk_list,
|
|
980
|
+
"content_type": ContentType.objects.get_for_model(model),
|
|
981
|
+
"delete_all": delete_all,
|
|
982
|
+
"filter_query_params": filter_query_params,
|
|
983
|
+
}
|
|
984
|
+
)
|
|
985
|
+
# BulkDeleteObjects job form cannot be invalid; Hence no handling of invalid case.
|
|
986
|
+
job_form.is_valid()
|
|
987
|
+
job_kwargs = BulkDeleteObjects.prepare_job_kwargs(job_form.cleaned_data)
|
|
988
|
+
job_result = JobResult.enqueue_job(
|
|
989
|
+
job_model,
|
|
990
|
+
request.user,
|
|
991
|
+
**BulkDeleteObjects.serialize_data(job_kwargs),
|
|
992
|
+
)
|
|
993
|
+
return redirect("extras:jobresult", pk=job_result.pk)
|
|
994
|
+
|
|
995
|
+
def send_bulk_edit_objects_to_job(self, request, form, model):
|
|
996
|
+
"""Prepare and enqueue a bulk edit job."""
|
|
997
|
+
job_model = Job.objects.get_for_class_path(BulkEditObjects.class_path)
|
|
998
|
+
form_data = normalize_querydict(request.POST, form)
|
|
999
|
+
if filterset_class := lookup.get_filterset_for_model(model):
|
|
1000
|
+
filter_query_params = normalize_querydict(request.GET, filterset=filterset_class())
|
|
1001
|
+
else:
|
|
1002
|
+
filter_query_params = {}
|
|
955
1003
|
|
|
956
|
-
|
|
1004
|
+
# Discarding non-filter query params
|
|
1005
|
+
new_filter_query_params = {}
|
|
1006
|
+
|
|
1007
|
+
for key, value in filter_query_params.items():
|
|
1008
|
+
try:
|
|
1009
|
+
filtering.get_filterset_field(filterset_class(), key)
|
|
1010
|
+
new_filter_query_params[key] = value
|
|
1011
|
+
except core_exceptions.FilterSetFieldNotFound:
|
|
1012
|
+
self.logger.debug(f"Query parameter `{key}` not found in `{filterset_class}`, discarding it")
|
|
1013
|
+
|
|
1014
|
+
filter_query_params = new_filter_query_params
|
|
1015
|
+
|
|
1016
|
+
job_form = BulkEditObjects.as_form(
|
|
1017
|
+
data={
|
|
1018
|
+
"form_data": form_data,
|
|
1019
|
+
"content_type": ContentType.objects.get_for_model(model),
|
|
1020
|
+
"edit_all": request.POST.get("_all") is not None,
|
|
1021
|
+
"filter_query_params": filter_query_params,
|
|
1022
|
+
}
|
|
1023
|
+
)
|
|
1024
|
+
# NOTE: BulkEditObjects cant be invalid, so there is no need for handling invalid error
|
|
1025
|
+
job_form.is_valid()
|
|
1026
|
+
job_kwargs = BulkEditObjects.prepare_job_kwargs(job_form.cleaned_data)
|
|
1027
|
+
job_result = JobResult.enqueue_job(
|
|
1028
|
+
job_model,
|
|
1029
|
+
request.user,
|
|
1030
|
+
**BulkEditObjects.serialize_data(job_kwargs),
|
|
1031
|
+
)
|
|
1032
|
+
return redirect("extras:jobresult", pk=job_result.pk)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, BulkEditAndBulkDeleteModelMixin):
|
|
957
1036
|
"""
|
|
958
1037
|
UI mixin to bulk destroy model instances.
|
|
959
1038
|
"""
|
|
960
1039
|
|
|
961
|
-
bulk_destroy_form_class = None
|
|
962
|
-
filterset_class = None
|
|
1040
|
+
bulk_destroy_form_class: Optional[type[Form]] = None
|
|
1041
|
+
filterset_class: Optional[type[FilterSet]] = None
|
|
963
1042
|
|
|
964
1043
|
def _process_bulk_destroy_form(self, form):
|
|
965
1044
|
request = self.request
|
|
@@ -992,7 +1071,8 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
|
|
|
992
1071
|
"""
|
|
993
1072
|
return self.perform_bulk_destroy(request, **kwargs)
|
|
994
1073
|
|
|
995
|
-
|
|
1074
|
+
# TODO: this conflicts with BulkDestroyModelMixin.perform_bulk_destroy(self, objects)
|
|
1075
|
+
def perform_bulk_destroy(self, request, **kwargs): # pylint:disable=arguments-renamed
|
|
996
1076
|
"""
|
|
997
1077
|
request.POST "_delete": Function to render the user selection of objects in a table form/BulkDestroyConfirmationForm via Response that is passed to NautobotHTMLRenderer.
|
|
998
1078
|
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.
|
|
@@ -1002,21 +1082,23 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
|
|
|
1002
1082
|
data = {}
|
|
1003
1083
|
# Are we deleting *all* objects in the queryset or just a selected subset?
|
|
1004
1084
|
if delete_all:
|
|
1085
|
+
self.pk_list = []
|
|
1005
1086
|
queryset = self._get_bulk_edit_delete_all_queryset(self.request)
|
|
1006
|
-
data = self._bulk_delete_all_context(request, queryset)
|
|
1007
1087
|
else:
|
|
1008
1088
|
self.pk_list = list(request.POST.getlist("pk"))
|
|
1089
|
+
queryset = queryset.filter(pk__in=self.pk_list)
|
|
1009
1090
|
|
|
1010
|
-
form_class = self.get_form_class(**kwargs)
|
|
1011
1091
|
if "_confirm" in request.POST:
|
|
1092
|
+
form_class = self.get_form_class(**kwargs)
|
|
1012
1093
|
form = form_class(request.POST, initial=normalize_querydict(request.GET, form_class=form_class))
|
|
1013
1094
|
if form.is_valid():
|
|
1014
|
-
return self.
|
|
1095
|
+
return self.send_bulk_delete_objects_to_job(request, self.pk_list, queryset.model, delete_all)
|
|
1015
1096
|
else:
|
|
1016
1097
|
return self.form_invalid(form)
|
|
1098
|
+
table = None
|
|
1017
1099
|
if not delete_all:
|
|
1018
1100
|
table_class = self.get_table_class()
|
|
1019
|
-
table = table_class(queryset
|
|
1101
|
+
table = table_class(queryset, orderable=False)
|
|
1020
1102
|
if not table.rows:
|
|
1021
1103
|
messages.warning(
|
|
1022
1104
|
request,
|
|
@@ -1024,7 +1106,13 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin, Ed
|
|
|
1024
1106
|
)
|
|
1025
1107
|
return redirect(self.get_return_url(request))
|
|
1026
1108
|
|
|
1027
|
-
|
|
1109
|
+
data.update(
|
|
1110
|
+
{
|
|
1111
|
+
"table": table,
|
|
1112
|
+
"total_objs_to_delete": queryset.count(),
|
|
1113
|
+
"delete_all": delete_all,
|
|
1114
|
+
}
|
|
1115
|
+
)
|
|
1028
1116
|
return Response(data)
|
|
1029
1117
|
|
|
1030
1118
|
|
|
@@ -1082,14 +1170,14 @@ class ObjectBulkCreateViewMixin(NautobotViewSetMixin): # 3.0 TODO: remove, unus
|
|
|
1082
1170
|
return self.form_invalid(form)
|
|
1083
1171
|
|
|
1084
1172
|
|
|
1085
|
-
class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin,
|
|
1173
|
+
class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, BulkEditAndBulkDeleteModelMixin):
|
|
1086
1174
|
"""
|
|
1087
1175
|
UI mixin to bulk update model instances.
|
|
1088
1176
|
"""
|
|
1089
1177
|
|
|
1090
|
-
filterset_class = None
|
|
1091
|
-
bulk_update_form_class = None
|
|
1178
|
+
filterset_class: ClassVar[Optional[type[FilterSet]]] = None
|
|
1092
1179
|
|
|
1180
|
+
# NOTE: Performing BulkEdit Objects has been moved to a system job, but the logic remains here to ensure backward compatibility.
|
|
1093
1181
|
def _process_bulk_update_form(self, form):
|
|
1094
1182
|
request = self.request
|
|
1095
1183
|
queryset = self.get_queryset()
|
|
@@ -1104,7 +1192,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
|
|
|
1104
1192
|
for field in form.fields
|
|
1105
1193
|
if field not in form_custom_fields + form_relationships + ["pk"] + ["object_note"]
|
|
1106
1194
|
]
|
|
1107
|
-
nullified_fields = request.POST.getlist("_nullify")
|
|
1195
|
+
nullified_fields = request.POST.getlist("_nullify") or []
|
|
1108
1196
|
with deferred_change_logging_for_bulk_operation():
|
|
1109
1197
|
updated_objects = []
|
|
1110
1198
|
edit_all = self.request.POST.get("_all")
|
|
@@ -1123,7 +1211,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
|
|
|
1123
1211
|
# This form field is used to modify a field rather than set its value directly
|
|
1124
1212
|
model_field = None
|
|
1125
1213
|
# Handle nullification
|
|
1126
|
-
if name in form.nullable_fields and name in nullified_fields:
|
|
1214
|
+
if name in form.nullable_fields and nullified_fields and name in nullified_fields:
|
|
1127
1215
|
if isinstance(model_field, ManyToManyField):
|
|
1128
1216
|
getattr(obj, name).set([])
|
|
1129
1217
|
else:
|
|
@@ -1137,7 +1225,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
|
|
|
1137
1225
|
setattr(obj, name, form.cleaned_data[name])
|
|
1138
1226
|
# Update custom fields
|
|
1139
1227
|
for field_name in form_custom_fields:
|
|
1140
|
-
if field_name in form.nullable_fields and field_name in nullified_fields:
|
|
1228
|
+
if field_name in form.nullable_fields and nullified_fields and field_name in nullified_fields:
|
|
1141
1229
|
obj.cf[remove_prefix_from_cf_key(field_name)] = None
|
|
1142
1230
|
elif form.cleaned_data.get(field_name) not in (None, "", []):
|
|
1143
1231
|
obj.cf[remove_prefix_from_cf_key(field_name)] = form.cleaned_data[field_name]
|
|
@@ -1200,7 +1288,7 @@ class ObjectBulkUpdateViewMixin(NautobotViewSetMixin, BulkUpdateModelMixin, Edit
|
|
|
1200
1288
|
form = form_class(queryset.model, request.POST, edit_all=edit_all)
|
|
1201
1289
|
restrict_form_fields(form, request.user)
|
|
1202
1290
|
if form.is_valid():
|
|
1203
|
-
return self.
|
|
1291
|
+
return self.send_bulk_edit_objects_to_job(self.request, form, queryset.model)
|
|
1204
1292
|
else:
|
|
1205
1293
|
return self.form_invalid(form)
|
|
1206
1294
|
table = None
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -244,12 +244,11 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
244
244
|
restrict_form_fields(form, request.user)
|
|
245
245
|
elif view.action == "bulk_destroy":
|
|
246
246
|
pk_list = getattr(view, "pk_list", [])
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
form = form_class(initial=initial)
|
|
247
|
+
initial = {
|
|
248
|
+
"pk": pk_list,
|
|
249
|
+
"return_url": return_url,
|
|
250
|
+
}
|
|
251
|
+
form = form_class(initial=initial)
|
|
253
252
|
delete_all = request.POST.get("_all")
|
|
254
253
|
if not delete_all:
|
|
255
254
|
table = self.construct_table(view, pk_list=pk_list)
|
|
@@ -347,6 +346,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
347
346
|
# Ensure the proper inheritance of context variables is applied: the view's returned data takes priority over the viewset's get_extra_context
|
|
348
347
|
context.update(view.get_extra_context(request, instance))
|
|
349
348
|
context.update(self.get_template_context(data, renderer_context))
|
|
349
|
+
|
|
350
350
|
return context
|
|
351
351
|
|
|
352
352
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -247,15 +247,15 @@ class LocationSerializer(
|
|
|
247
247
|
# https://www.django-rest-framework.org/api-guide/validators/#optional-fields
|
|
248
248
|
validators = []
|
|
249
249
|
|
|
250
|
-
def validate(self,
|
|
250
|
+
def validate(self, attrs):
|
|
251
251
|
# Validate uniqueness of (parent, name) since we omitted the automatically created validator from Meta.
|
|
252
|
-
if
|
|
252
|
+
if attrs.get("parent") and attrs.get("name"):
|
|
253
253
|
validator = UniqueTogetherValidator(queryset=Location.objects.all(), fields=("parent", "name"))
|
|
254
|
-
validator(
|
|
254
|
+
validator(attrs, self)
|
|
255
255
|
|
|
256
|
-
super().validate(
|
|
256
|
+
super().validate(attrs)
|
|
257
257
|
|
|
258
|
-
return
|
|
258
|
+
return attrs
|
|
259
259
|
|
|
260
260
|
|
|
261
261
|
#
|
|
@@ -285,20 +285,20 @@ class RackSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
285
285
|
# This prevents facility_id and rack_group from being interpreted as required fields.
|
|
286
286
|
validators = []
|
|
287
287
|
|
|
288
|
-
def validate(self,
|
|
288
|
+
def validate(self, attrs):
|
|
289
289
|
# Validate uniqueness of (rack_group, name) since we omitted the automatically-created validator above.
|
|
290
|
-
if
|
|
290
|
+
if attrs.get("rack_group", None):
|
|
291
291
|
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "name"))
|
|
292
|
-
validator(
|
|
292
|
+
validator(attrs, self)
|
|
293
293
|
# Validate uniqueness of (rack_group, facility_id) since we omitted the automatically-created validator above.
|
|
294
|
-
if
|
|
294
|
+
if attrs.get("facility_id", None) and attrs.get("rack_group", None):
|
|
295
295
|
validator = UniqueTogetherValidator(queryset=Rack.objects.all(), fields=("rack_group", "facility_id"))
|
|
296
|
-
validator(
|
|
296
|
+
validator(attrs, self)
|
|
297
297
|
|
|
298
298
|
# Enforce model validation
|
|
299
|
-
super().validate(
|
|
299
|
+
super().validate(attrs)
|
|
300
300
|
|
|
301
|
-
return
|
|
301
|
+
return attrs
|
|
302
302
|
|
|
303
303
|
|
|
304
304
|
class RackUnitSerializer(serializers.Serializer):
|
|
@@ -369,7 +369,7 @@ class ManufacturerSerializer(NautobotModelSerializer):
|
|
|
369
369
|
fields = "__all__"
|
|
370
370
|
|
|
371
371
|
|
|
372
|
-
class DeviceFamilySerializer(NautobotModelSerializer):
|
|
372
|
+
class DeviceFamilySerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
373
373
|
device_type_count = serializers.IntegerField(read_only=True)
|
|
374
374
|
|
|
375
375
|
class Meta:
|
|
@@ -534,14 +534,6 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
534
534
|
"vc_priority": {"label": "Virtual chassis priority"},
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
-
def get_additional_detail_view_tabs(self):
|
|
538
|
-
"""Add "Virtual Chassis" as a separate detail tab."""
|
|
539
|
-
tabs = super().get_additional_detail_view_tabs()
|
|
540
|
-
tabs["Virtual Chassis"] = [
|
|
541
|
-
{"Virtual Chassis": {"fields": ["virtual_chassis", "vc_position", "vc_priority"]}},
|
|
542
|
-
]
|
|
543
|
-
return tabs
|
|
544
|
-
|
|
545
537
|
def get_field_names(self, declared_fields, info):
|
|
546
538
|
"""
|
|
547
539
|
Add a couple of special fields to the serializer.
|
|
@@ -558,20 +550,20 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
558
550
|
def get_config_context(self, obj):
|
|
559
551
|
return obj.get_config_context()
|
|
560
552
|
|
|
561
|
-
def validate(self,
|
|
553
|
+
def validate(self, attrs):
|
|
562
554
|
# Validate uniqueness of (rack, position, face) since we omitted the automatically-created validator from Meta.
|
|
563
|
-
if
|
|
555
|
+
if attrs.get("rack") and attrs.get("position") and attrs.get("face"):
|
|
564
556
|
validator = UniqueTogetherValidator(
|
|
565
557
|
queryset=Device.objects.all(),
|
|
566
558
|
fields=("rack", "position", "face"),
|
|
567
559
|
message=f"The position and face is already occupied on this rack. {UniqueTogetherValidator.message}",
|
|
568
560
|
)
|
|
569
|
-
validator(
|
|
561
|
+
validator(attrs, self)
|
|
570
562
|
|
|
571
563
|
# Enforce model validation
|
|
572
|
-
super().validate(
|
|
564
|
+
super().validate(attrs)
|
|
573
565
|
|
|
574
|
-
return
|
|
566
|
+
return attrs
|
|
575
567
|
|
|
576
568
|
|
|
577
569
|
class DeviceNAPALMSerializer(serializers.Serializer):
|
|
@@ -644,22 +636,22 @@ class PowerPortSerializer(
|
|
|
644
636
|
|
|
645
637
|
|
|
646
638
|
class InterfaceCommonSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
647
|
-
def validate(self,
|
|
639
|
+
def validate(self, attrs):
|
|
648
640
|
# Validate many-to-many VLAN assignments
|
|
649
|
-
mode =
|
|
641
|
+
mode = attrs.get("mode", getattr(self.instance, "mode", None))
|
|
650
642
|
|
|
651
643
|
if mode != InterfaceModeChoices.MODE_TAGGED:
|
|
652
|
-
if
|
|
644
|
+
if attrs.get("tagged_vlans"):
|
|
653
645
|
raise serializers.ValidationError(
|
|
654
646
|
{
|
|
655
647
|
"tagged_vlans": f"Mode must be set to {InterfaceModeChoices.MODE_TAGGED} when specifying tagged_vlans"
|
|
656
648
|
}
|
|
657
649
|
)
|
|
658
650
|
|
|
659
|
-
if
|
|
651
|
+
if attrs.get("tagged_vlans") != [] and self.instance and self.instance.tagged_vlans.exists():
|
|
660
652
|
raise serializers.ValidationError({"tagged_vlans": f"Clear tagged_vlans to set mode to {mode}"})
|
|
661
653
|
|
|
662
|
-
return super().validate(
|
|
654
|
+
return super().validate(attrs)
|
|
663
655
|
|
|
664
656
|
|
|
665
657
|
class InterfaceSerializer(
|
|
@@ -671,7 +663,7 @@ class InterfaceSerializer(
|
|
|
671
663
|
type = ChoiceField(choices=InterfaceTypeChoices)
|
|
672
664
|
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
|
|
673
665
|
mac_address = serializers.CharField(allow_blank=True, allow_null=True, required=False)
|
|
674
|
-
ip_address_count = serializers.IntegerField(read_only=True)
|
|
666
|
+
ip_address_count = serializers.IntegerField(read_only=True, source="_ip_address_count")
|
|
675
667
|
|
|
676
668
|
class Meta:
|
|
677
669
|
model = Interface
|
|
@@ -760,18 +752,18 @@ class InventoryItemSerializer(TaggedModelSerializerMixin, TreeModelSerializerMix
|
|
|
760
752
|
# https://www.django-rest-framework.org/api-guide/validators/#optional-fields
|
|
761
753
|
validators = []
|
|
762
754
|
|
|
763
|
-
def validate(self,
|
|
755
|
+
def validate(self, attrs):
|
|
764
756
|
# Validate uniqueness of (device, parent, name) since we omitted the automatically created validator from Meta.
|
|
765
|
-
if
|
|
757
|
+
if attrs.get("device") and attrs.get("parent") and attrs.get("name"):
|
|
766
758
|
validator = UniqueTogetherValidator(
|
|
767
759
|
queryset=InventoryItem.objects.all(),
|
|
768
760
|
fields=("device", "parent", "name"),
|
|
769
761
|
)
|
|
770
|
-
validator(
|
|
762
|
+
validator(attrs, self)
|
|
771
763
|
|
|
772
|
-
super().validate(
|
|
764
|
+
super().validate(attrs)
|
|
773
765
|
|
|
774
|
-
return
|
|
766
|
+
return attrs
|
|
775
767
|
|
|
776
768
|
|
|
777
769
|
#
|
|
@@ -989,7 +981,7 @@ class DeviceTypeToSoftwareImageFileSerializer(ValidatedModelSerializer):
|
|
|
989
981
|
|
|
990
982
|
class ControllerSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
991
983
|
capabilities = serializers.ListField(
|
|
992
|
-
child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True
|
|
984
|
+
child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True, required=False
|
|
993
985
|
)
|
|
994
986
|
|
|
995
987
|
class Meta:
|
|
@@ -999,7 +991,7 @@ class ControllerSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
999
991
|
|
|
1000
992
|
class ControllerManagedDeviceGroupSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
1001
993
|
capabilities = serializers.ListField(
|
|
1002
|
-
child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True
|
|
994
|
+
child=ChoiceField(choices=ControllerCapabilitiesChoices, required=False), allow_empty=True, required=False
|
|
1003
995
|
)
|
|
1004
996
|
|
|
1005
997
|
class Meta:
|
|
@@ -1018,21 +1010,21 @@ class ModuleBaySerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
1018
1010
|
fields = "__all__"
|
|
1019
1011
|
validators = []
|
|
1020
1012
|
|
|
1021
|
-
def validate(self,
|
|
1013
|
+
def validate(self, attrs):
|
|
1022
1014
|
"""Validate device and module field constraints for module bay."""
|
|
1023
|
-
if
|
|
1015
|
+
if attrs.get("parent_device") and attrs.get("parent_module"):
|
|
1024
1016
|
raise serializers.ValidationError("Only one of parent_device or parent_module must be set")
|
|
1025
|
-
if
|
|
1017
|
+
if attrs.get("parent_device"):
|
|
1026
1018
|
validator = UniqueTogetherValidator(
|
|
1027
1019
|
queryset=self.Meta.model.objects.all(), fields=("parent_device", "name")
|
|
1028
1020
|
)
|
|
1029
|
-
validator(
|
|
1030
|
-
if
|
|
1021
|
+
validator(attrs, self)
|
|
1022
|
+
if attrs.get("parent_module"):
|
|
1031
1023
|
validator = UniqueTogetherValidator(
|
|
1032
1024
|
queryset=self.Meta.model.objects.all(), fields=("parent_module", "name")
|
|
1033
1025
|
)
|
|
1034
|
-
validator(
|
|
1035
|
-
return super().validate(
|
|
1026
|
+
validator(attrs, self)
|
|
1027
|
+
return super().validate(attrs)
|
|
1036
1028
|
|
|
1037
1029
|
|
|
1038
1030
|
class ModuleBayTemplateSerializer(NautobotModelSerializer):
|
|
@@ -1041,17 +1033,17 @@ class ModuleBayTemplateSerializer(NautobotModelSerializer):
|
|
|
1041
1033
|
fields = "__all__"
|
|
1042
1034
|
validators = []
|
|
1043
1035
|
|
|
1044
|
-
def validate(self,
|
|
1036
|
+
def validate(self, attrs):
|
|
1045
1037
|
"""Validate device_type and module_type field constraints for module bay template."""
|
|
1046
|
-
if
|
|
1038
|
+
if attrs.get("device_type") and attrs.get("module_type"):
|
|
1047
1039
|
raise serializers.ValidationError("Only one of device_type or module_type must be set")
|
|
1048
|
-
if
|
|
1040
|
+
if attrs.get("device_type"):
|
|
1049
1041
|
validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("device_type", "name"))
|
|
1050
|
-
validator(
|
|
1051
|
-
if
|
|
1042
|
+
validator(attrs, self)
|
|
1043
|
+
if attrs.get("module_type"):
|
|
1052
1044
|
validator = UniqueTogetherValidator(queryset=self.Meta.model.objects.all(), fields=("module_type", "name"))
|
|
1053
|
-
validator(
|
|
1054
|
-
return super().validate(
|
|
1045
|
+
validator(attrs, self)
|
|
1046
|
+
return super().validate(attrs)
|
|
1055
1047
|
|
|
1056
1048
|
|
|
1057
1049
|
class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
@@ -1061,17 +1053,17 @@ class ModuleSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
1061
1053
|
|
|
1062
1054
|
validators = []
|
|
1063
1055
|
|
|
1064
|
-
def validate(self,
|
|
1056
|
+
def validate(self, attrs):
|
|
1065
1057
|
"""Validate asset_Tag, serial, parent_module_bay and location field constraints for module."""
|
|
1066
|
-
if
|
|
1058
|
+
if attrs.get("parent_module_bay") and attrs.get("location"):
|
|
1067
1059
|
raise serializers.ValidationError("Only one of parent_module_bay or location must be set")
|
|
1068
|
-
if
|
|
1060
|
+
if attrs.get("serial"):
|
|
1069
1061
|
validator = UniqueTogetherValidator(queryset=Module.objects.all(), fields=("module_type", "serial"))
|
|
1070
|
-
validator(
|
|
1071
|
-
if
|
|
1062
|
+
validator(attrs, self)
|
|
1063
|
+
if attrs.get("asset_tag"):
|
|
1072
1064
|
validator = UniqueValidator(queryset=Module.objects.all())
|
|
1073
|
-
validator(
|
|
1074
|
-
return super().validate(
|
|
1065
|
+
validator(attrs["asset_tag"], self.fields["asset_tag"])
|
|
1066
|
+
return super().validate(attrs)
|
|
1075
1067
|
|
|
1076
1068
|
|
|
1077
1069
|
class ModuleTypeSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
@@ -1080,16 +1072,16 @@ class ModuleTypeSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
1080
1072
|
fields = "__all__"
|
|
1081
1073
|
|
|
1082
1074
|
|
|
1083
|
-
class VirtualDeviceContextSerializer(NautobotModelSerializer):
|
|
1075
|
+
class VirtualDeviceContextSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
1084
1076
|
class Meta:
|
|
1085
1077
|
model = VirtualDeviceContext
|
|
1086
1078
|
fields = "__all__"
|
|
1087
1079
|
|
|
1088
|
-
def validate(self,
|
|
1080
|
+
def validate(self, attrs):
|
|
1089
1081
|
"""Validate device cannot be changed for VirtualDeviceContext."""
|
|
1090
|
-
if
|
|
1082
|
+
if attrs.get("device") and self.instance and self.instance.device != attrs.get("device"):
|
|
1091
1083
|
raise serializers.ValidationError("Changing the device of a VirtualDeviceContext is not allowed.")
|
|
1092
|
-
return super().validate(
|
|
1084
|
+
return super().validate(attrs)
|
|
1093
1085
|
|
|
1094
1086
|
|
|
1095
1087
|
class InterfaceVDCAssignmentSerializer(ValidatedModelSerializer):
|