nautobot 2.0.0a2__py3-none-any.whl → 2.0.0b1__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.
- nautobot/__init__.py +1 -5
- nautobot/apps/api.py +6 -8
- nautobot/apps/forms.py +0 -2
- nautobot/apps/ui.py +0 -8
- nautobot/circuits/api/serializers.py +9 -119
- nautobot/circuits/api/urls.py +1 -1
- nautobot/circuits/api/views.py +0 -1
- nautobot/circuits/choices.py +0 -2
- nautobot/circuits/filters.py +7 -6
- nautobot/circuits/forms.py +3 -73
- nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
- nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
- nautobot/circuits/migrations/0003_auto_slug.py +0 -1
- nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
- nautobot/circuits/migrations/0005_providernetwork.py +0 -1
- nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
- nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
- nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
- nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
- nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
- nautobot/circuits/migrations/0012_created_datetime.py +0 -1
- nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
- nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
- nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
- nautobot/circuits/migrations/0016_tagsfield.py +34 -0
- nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
- nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
- nautobot/circuits/models.py +3 -93
- nautobot/circuits/navigation.py +14 -69
- nautobot/circuits/signals.py +0 -2
- nautobot/circuits/tables.py +42 -5
- nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/provider_create.html +0 -1
- nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
- nautobot/circuits/tests/integration/test_relationships.py +13 -16
- nautobot/circuits/tests/test_api.py +13 -43
- nautobot/circuits/tests/test_filters.py +20 -15
- nautobot/circuits/tests/test_models.py +7 -3
- nautobot/circuits/tests/test_views.py +57 -67
- nautobot/circuits/views.py +18 -9
- nautobot/core/api/__init__.py +8 -2
- nautobot/core/api/authentication.py +0 -3
- nautobot/core/api/fields.py +15 -6
- nautobot/core/api/filter_backends.py +3 -2
- nautobot/core/api/metadata.py +237 -30
- nautobot/core/api/mixins.py +94 -0
- nautobot/core/api/pagination.py +3 -3
- nautobot/core/api/parsers.py +154 -0
- nautobot/core/api/renderers.py +153 -2
- nautobot/core/api/schema.py +47 -3
- nautobot/core/api/serializers.py +377 -37
- nautobot/core/api/urls.py +11 -3
- nautobot/core/api/utils.py +174 -2
- nautobot/core/api/versioning.py +32 -10
- nautobot/core/api/views.py +266 -75
- nautobot/core/apps/__init__.py +138 -221
- nautobot/core/celery/__init__.py +112 -41
- nautobot/core/celery/backends.py +19 -13
- nautobot/core/celery/control.py +46 -0
- nautobot/core/celery/encoders.py +53 -0
- nautobot/core/celery/log.py +38 -0
- nautobot/core/celery/schedulers.py +23 -4
- nautobot/core/celery/task.py +1 -16
- nautobot/core/checks.py +0 -27
- nautobot/core/choices.py +21 -113
- nautobot/core/{cli.py → cli/__init__.py} +1 -2
- nautobot/core/cli/__main__.py +3 -0
- nautobot/core/constants.py +25 -43
- nautobot/core/context_processors.py +12 -0
- nautobot/core/filters.py +2 -2
- nautobot/core/forms/__init__.py +0 -4
- nautobot/core/forms/fields.py +39 -68
- nautobot/core/forms/forms.py +27 -27
- nautobot/core/forms/utils.py +7 -59
- nautobot/core/forms/widgets.py +0 -1
- nautobot/core/graphql/__init__.py +2 -2
- nautobot/core/graphql/schema.py +4 -27
- nautobot/core/jobs/__init__.py +75 -0
- nautobot/core/management/commands/build_ui.py +255 -0
- nautobot/core/management/commands/celery.py +0 -1
- nautobot/core/management/commands/generate_test_data.py +18 -13
- nautobot/core/management/commands/post_upgrade.py +24 -24
- nautobot/core/management/commands/validate_models.py +0 -1
- nautobot/core/middleware.py +0 -1
- nautobot/core/models/__init__.py +26 -1
- nautobot/core/models/fields.py +24 -5
- nautobot/core/models/generics.py +2 -46
- nautobot/core/models/managers.py +5 -0
- nautobot/core/models/name_color_content_types.py +1 -19
- nautobot/core/models/tree_queries.py +14 -4
- nautobot/core/models/utils.py +9 -10
- nautobot/core/models/validators.py +17 -8
- nautobot/core/releases.py +8 -10
- nautobot/core/settings.py +81 -53
- nautobot/core/tables.py +5 -5
- nautobot/core/tasks.py +4 -7
- nautobot/core/templates/base.html +1 -49
- nautobot/core/templates/base_django.html +49 -0
- nautobot/core/templates/base_react.html +55 -0
- nautobot/core/templates/buttons/export.html +6 -4
- nautobot/core/templates/generic/object_bulk_create.html +10 -21
- nautobot/core/templates/generic/object_list.html +4 -1
- nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
- nautobot/core/templates/inc/footer.html +1 -0
- nautobot/core/templates/inc/javascript.html +0 -14
- nautobot/core/templates/inc/nav_menu.html +28 -33
- nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
- nautobot/core/templates/inc/relationships_table_rows.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +8 -25
- nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
- nautobot/core/templates/rest_framework/api.html +8 -0
- nautobot/core/templatetags/buttons.py +32 -29
- nautobot/core/templatetags/helpers.py +1 -1
- nautobot/core/testing/__init__.py +47 -44
- nautobot/core/testing/api.py +365 -47
- nautobot/core/testing/filters.py +12 -7
- nautobot/core/testing/integration.py +1 -1
- nautobot/core/testing/migrations.py +2 -0
- nautobot/core/testing/mixins.py +22 -12
- nautobot/core/testing/schema.py +2 -1
- nautobot/core/testing/views.py +28 -51
- nautobot/core/tests/integration/test_filters.py +17 -8
- nautobot/core/tests/integration/test_navbar.py +11 -34
- nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
- nautobot/core/tests/nautobot_config.py +2 -3
- nautobot/core/tests/runner.py +0 -1
- nautobot/core/tests/test_api.py +290 -24
- nautobot/core/tests/test_authentication.py +57 -14
- nautobot/core/tests/test_checks.py +0 -7
- nautobot/core/tests/test_choices.py +0 -1
- nautobot/core/tests/test_filters.py +117 -110
- nautobot/core/tests/test_forms.py +47 -110
- nautobot/core/tests/test_graphql.py +158 -135
- nautobot/core/tests/test_logging.py +4 -1
- nautobot/core/tests/test_managers.py +3 -5
- nautobot/core/tests/test_models.py +2 -0
- nautobot/core/tests/test_ordering.py +0 -2
- nautobot/core/tests/test_paginator.py +3 -1
- nautobot/core/tests/test_releases.py +12 -12
- nautobot/core/tests/test_templatetags_helpers.py +7 -4
- nautobot/core/tests/test_utils.py +112 -78
- nautobot/core/tests/test_views.py +12 -17
- nautobot/core/tests/test_views_utils.py +6 -9
- nautobot/core/utils/data.py +17 -0
- nautobot/core/utils/deprecation.py +13 -20
- nautobot/core/utils/filtering.py +53 -9
- nautobot/core/utils/git.py +12 -4
- nautobot/core/utils/lookup.py +3 -1
- nautobot/core/utils/requests.py +23 -116
- nautobot/core/views/__init__.py +1 -2
- nautobot/core/views/generic.py +131 -119
- nautobot/core/views/mixins.py +53 -62
- nautobot/core/views/paginator.py +0 -1
- nautobot/core/views/renderers.py +14 -12
- nautobot/core/views/utils.py +87 -4
- nautobot/dcim/api/serializers.py +160 -672
- nautobot/dcim/api/urls.py +1 -1
- nautobot/dcim/api/views.py +7 -46
- nautobot/dcim/choices.py +2 -25
- nautobot/dcim/elevations.py +0 -1
- nautobot/dcim/factory.py +15 -4
- nautobot/dcim/filters/__init__.py +42 -13
- nautobot/dcim/form_mixins.py +1 -27
- nautobot/dcim/forms.py +58 -797
- nautobot/dcim/management/commands/trace_paths.py +0 -1
- nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
- nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
- nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
- nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
- nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
- nautobot/dcim/migrations/0006_auto_slug.py +0 -1
- nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
- nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
- nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
- nautobot/dcim/migrations/0010_interface_status.py +0 -1
- nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
- nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
- nautobot/dcim/migrations/0013_location_location_type.py +0 -1
- nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
- nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
- nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
- nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
- nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
- nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
- nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
- nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
- nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
- nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
- nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
- nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
- nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
- nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
- nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
- nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
- nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
- nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
- nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
- nautobot/dcim/migrations/0033_created_datetime.py +0 -1
- nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
- nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
- nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
- nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
- nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
- nautobot/dcim/migrations/0039_remove_slug.py +24 -0
- nautobot/dcim/migrations/0040_tagsfield.py +109 -0
- nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
- nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
- nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
- nautobot/dcim/models/cables.py +4 -35
- nautobot/dcim/models/device_component_templates.py +7 -2
- nautobot/dcim/models/device_components.py +26 -203
- nautobot/dcim/models/devices.py +30 -152
- nautobot/dcim/models/locations.py +3 -64
- nautobot/dcim/models/power.py +3 -51
- nautobot/dcim/models/racks.py +7 -86
- nautobot/dcim/navigation.py +141 -467
- nautobot/dcim/signals.py +0 -2
- nautobot/dcim/tables/devices.py +8 -5
- nautobot/dcim/tables/devicetypes.py +1 -1
- nautobot/dcim/tables/locations.py +2 -2
- nautobot/dcim/tables/power.py +2 -2
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/device.html +15 -4
- nautobot/dcim/templates/dcim/device_edit.html +6 -0
- nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
- nautobot/dcim/templates/dcim/devicetype.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +4 -0
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/location.html +16 -1
- nautobot/dcim/templates/dcim/locationtype.html +15 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/rackgroup.html +0 -12
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +202 -130
- nautobot/dcim/tests/test_cablepaths.py +47 -42
- nautobot/dcim/tests/test_filters.py +156 -134
- nautobot/dcim/tests/test_forms.py +12 -213
- nautobot/dcim/tests/test_graphql.py +8 -3
- nautobot/dcim/tests/test_migrations.py +6 -11
- nautobot/dcim/tests/test_models.py +208 -158
- nautobot/dcim/tests/test_natural_ordering.py +12 -14
- nautobot/dcim/tests/test_signals.py +7 -4
- nautobot/dcim/tests/test_views.py +270 -264
- nautobot/dcim/urls.py +21 -26
- nautobot/dcim/views.py +14 -156
- nautobot/docs/additional-features/caching.md +6 -87
- nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
- nautobot/docs/additional-features/jobs.md +179 -197
- nautobot/docs/administration/nautobot-server.md +9 -24
- nautobot/docs/administration/nautobot-shell.md +6 -6
- nautobot/docs/administration/replicating-nautobot.md +0 -10
- nautobot/docs/configuration/index.md +9 -9
- nautobot/docs/configuration/optional-settings.md +32 -61
- nautobot/docs/configuration/required-settings.md +11 -52
- nautobot/docs/development/application-registry.md +2 -13
- nautobot/docs/development/best-practices.md +2 -1
- nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
- nautobot/docs/development/extending-models.md +15 -17
- nautobot/docs/development/generic-views.md +0 -2
- nautobot/docs/development/getting-started.md +56 -6
- nautobot/docs/development/navigation-menu.md +22 -93
- nautobot/docs/development/react-ui.md +105 -0
- nautobot/docs/development/release-checklist.md +3 -3
- nautobot/docs/development/role-internals.md +1 -3
- nautobot/docs/development/style-guide.md +6 -4
- nautobot/docs/development/templates.md +2 -1
- nautobot/docs/docker/index.md +16 -14
- nautobot/docs/index.md +7 -3
- nautobot/docs/installation/index.md +4 -1
- nautobot/docs/installation/migrating-from-netbox.md +12 -43
- nautobot/docs/installation/migrating-from-postgresql.md +1 -1
- nautobot/docs/installation/nautobot.md +1 -1
- nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
- nautobot/docs/installation/upgrading.md +5 -2
- nautobot/docs/models/dcim/device.md +3 -0
- nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
- nautobot/docs/models/extras/computedfield.md +4 -4
- nautobot/docs/models/extras/dynamicgroup.md +9 -9
- nautobot/docs/models/extras/gitrepository.md +3 -0
- nautobot/docs/models/extras/job.md +1 -0
- nautobot/docs/models/extras/jobbutton.md +18 -13
- nautobot/docs/models/extras/jobhook.md +7 -4
- nautobot/docs/models/extras/jobresult.md +6 -2
- nautobot/docs/models/extras/relationship.md +2 -2
- nautobot/docs/models/extras/status.md +6 -19
- nautobot/docs/models/ipam/ipaddress.md +3 -0
- nautobot/docs/models/ipam/vrf.md +0 -3
- nautobot/docs/models/virtualization/virtualmachine.md +3 -0
- nautobot/docs/plugins/development.md +92 -24
- nautobot/docs/release-notes/version-1.5.md +96 -0
- nautobot/docs/release-notes/version-2.0.md +216 -0
- nautobot/docs/requirements.txt +5 -4
- nautobot/docs/rest-api/overview.md +384 -215
- nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
- nautobot/extras/admin.py +3 -5
- nautobot/extras/api/customfields.py +15 -39
- nautobot/extras/api/fields.py +0 -11
- nautobot/extras/api/mixins.py +45 -0
- nautobot/extras/api/relationships.py +63 -159
- nautobot/extras/api/serializers.py +165 -706
- nautobot/extras/api/urls.py +1 -1
- nautobot/extras/api/views.py +295 -282
- nautobot/extras/apps.py +4 -7
- nautobot/extras/choices.py +11 -22
- nautobot/extras/constants.py +9 -3
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +135 -186
- nautobot/extras/datasources/registry.py +25 -35
- nautobot/extras/factory.py +1 -3
- nautobot/extras/filters/__init__.py +49 -47
- nautobot/extras/filters/mixins.py +10 -8
- nautobot/extras/forms/forms.py +72 -148
- nautobot/extras/forms/mixins.py +34 -57
- nautobot/extras/health_checks.py +0 -33
- nautobot/extras/jobs.py +387 -566
- nautobot/extras/management/__init__.py +55 -48
- nautobot/extras/management/commands/renaturalize.py +0 -1
- nautobot/extras/management/commands/runjob.py +24 -62
- nautobot/extras/management/commands/webhook_receiver.py +0 -1
- nautobot/extras/managers.py +30 -7
- nautobot/extras/migrations/0001_initial_part_1.py +0 -1
- nautobot/extras/migrations/0002_initial_part_2.py +0 -1
- nautobot/extras/migrations/0003_initial_part_3.py +0 -1
- nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
- nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
- nautobot/extras/migrations/0006_graphqlquery.py +0 -1
- nautobot/extras/migrations/0007_configcontextschema.py +0 -1
- nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
- nautobot/extras/migrations/0009_computedfield.py +0 -1
- nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
- nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
- nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
- nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
- nautobot/extras/migrations/0014_auto_slug.py +0 -1
- nautobot/extras/migrations/0015_scheduled_job.py +0 -1
- nautobot/extras/migrations/0016_secret.py +0 -1
- nautobot/extras/migrations/0017_joblogentry.py +0 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
- nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
- nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
- nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
- nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
- nautobot/extras/migrations/0023_job_model.py +0 -1
- nautobot/extras/migrations/0024_job_data_migration.py +0 -1
- nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
- nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
- nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
- nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
- nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
- nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
- nautobot/extras/migrations/0031_tag_content_types.py +0 -1
- nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
- nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
- nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
- nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
- nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
- nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
- nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
- nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
- nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
- nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
- nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
- nautobot/extras/migrations/0043_note.py +0 -1
- nautobot/extras/migrations/0044_add_job_hook.py +0 -1
- nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
- nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
- nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
- nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
- nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
- nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
- nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
- nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
- nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
- nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
- nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
- nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
- nautobot/extras/migrations/0057_jobbutton.py +0 -1
- nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
- nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
- nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
- nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
- nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
- nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
- nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
- nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
- nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
- nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
- nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
- nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
- nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
- nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
- nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
- nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
- nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
- nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
- nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
- nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
- nautobot/extras/migrations/0078_remove_slug.py +45 -0
- nautobot/extras/migrations/0079_tagsfield.py +28 -0
- nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
- nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
- nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
- nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
- nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
- nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
- nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
- nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
- nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
- nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
- nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
- nautobot/extras/models/__init__.py +2 -3
- nautobot/extras/models/change_logging.py +0 -36
- nautobot/extras/models/customfields.py +39 -33
- nautobot/extras/models/datasources.py +48 -50
- nautobot/extras/models/groups.py +5 -12
- nautobot/extras/models/jobs.py +190 -323
- nautobot/extras/models/mixins.py +0 -71
- nautobot/extras/models/models.py +1 -22
- nautobot/extras/models/relationships.py +20 -21
- nautobot/extras/models/roles.py +0 -23
- nautobot/extras/models/secrets.py +2 -31
- nautobot/extras/models/statuses.py +6 -5
- nautobot/extras/models/tags.py +2 -17
- nautobot/extras/navigation.py +89 -307
- nautobot/extras/plugins/__init__.py +3 -121
- nautobot/extras/plugins/utils.py +0 -3
- nautobot/extras/plugins/validators.py +5 -4
- nautobot/extras/plugins/views.py +16 -4
- nautobot/extras/querysets.py +1 -7
- nautobot/extras/registry.py +3 -0
- nautobot/extras/signals.py +26 -60
- nautobot/extras/tables.py +42 -49
- nautobot/extras/tasks.py +0 -12
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +16 -1
- nautobot/extras/templates/extras/customfield.html +0 -13
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
- nautobot/extras/templates/extras/gitrepository.html +3 -3
- nautobot/extras/templates/extras/inc/jobresult.html +10 -0
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
- nautobot/extras/templates/extras/job.html +35 -25
- nautobot/extras/templates/extras/job_approval_request.html +15 -30
- nautobot/extras/templates/extras/job_detail.html +13 -31
- nautobot/extras/templates/extras/job_edit.html +14 -17
- nautobot/extras/templates/extras/jobresult.html +24 -6
- nautobot/extras/templates/extras/objectchange_list.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +2 -2
- nautobot/extras/templates/extras/secret.html +28 -0
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
- nautobot/extras/templatetags/custom_links.py +0 -2
- nautobot/extras/templatetags/job_buttons.py +1 -0
- nautobot/extras/templatetags/plugins.py +0 -1
- nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
- nautobot/extras/test_jobs/atomic_transaction.py +53 -0
- nautobot/extras/test_jobs/dry_run.py +29 -0
- nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
- nautobot/extras/test_jobs/duplicate_name2.py +9 -0
- nautobot/extras/test_jobs/fail.py +23 -0
- nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
- nautobot/extras/test_jobs/file_upload_pass.py +25 -0
- nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
- nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
- nautobot/extras/test_jobs/job_button_receiver.py +28 -0
- nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
- nautobot/extras/test_jobs/job_variables.py +88 -0
- nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
- nautobot/extras/test_jobs/log_redaction.py +20 -0
- nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
- nautobot/extras/test_jobs/modify_db.py +25 -0
- nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
- nautobot/extras/test_jobs/object_var_optional.py +21 -0
- nautobot/extras/test_jobs/object_var_required.py +21 -0
- nautobot/extras/test_jobs/object_vars.py +26 -0
- nautobot/extras/test_jobs/pass.py +25 -0
- nautobot/extras/test_jobs/profiling.py +32 -0
- nautobot/extras/test_jobs/read_only_job.py +15 -0
- nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
- nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
- nautobot/extras/tests/integration/__init__.py +3 -3
- nautobot/extras/tests/integration/test_computedfields.py +1 -1
- nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
- nautobot/extras/tests/integration/test_customfields.py +4 -2
- nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
- nautobot/extras/tests/integration/test_jobs.py +25 -27
- nautobot/extras/tests/integration/test_notes.py +8 -4
- nautobot/extras/tests/integration/test_plugins.py +4 -4
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_api.py +371 -381
- nautobot/extras/tests/test_changelog.py +17 -16
- nautobot/extras/tests/test_context_managers.py +5 -6
- nautobot/extras/tests/test_customfields.py +112 -73
- nautobot/extras/tests/test_datasources.py +191 -117
- nautobot/extras/tests/test_dynamicgroups.py +45 -68
- nautobot/extras/tests/test_filters.py +170 -130
- nautobot/extras/tests/test_forms.py +107 -109
- nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
- nautobot/extras/tests/test_jobs.py +271 -273
- nautobot/extras/tests/test_management.py +3 -6
- nautobot/extras/tests/test_migrations.py +5 -3
- nautobot/extras/tests/test_models.py +121 -173
- nautobot/extras/tests/test_notes.py +0 -1
- nautobot/extras/tests/test_plugins.py +55 -89
- nautobot/extras/tests/test_relationships.py +174 -130
- nautobot/extras/tests/test_tags.py +6 -12
- nautobot/extras/tests/test_utils.py +31 -1
- nautobot/extras/tests/test_views.py +223 -184
- nautobot/extras/tests/test_webhooks.py +16 -15
- nautobot/extras/urls.py +69 -69
- nautobot/extras/utils.py +137 -163
- nautobot/extras/views.py +81 -153
- nautobot/ipam/api/fields.py +17 -0
- nautobot/ipam/api/serializers.py +77 -164
- nautobot/ipam/api/urls.py +4 -1
- nautobot/ipam/api/views.py +28 -19
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/choices.py +5 -12
- nautobot/ipam/constants.py +1 -0
- nautobot/ipam/factory.py +41 -30
- nautobot/ipam/filters.py +58 -25
- nautobot/ipam/forms.py +82 -211
- nautobot/ipam/graphql/types.py +0 -9
- nautobot/ipam/lookups.py +13 -8
- nautobot/ipam/management/commands/__init__.py +0 -0
- nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
- nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
- nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
- nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
- nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
- nautobot/ipam/migrations/0005_auto_slug.py +0 -1
- nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
- nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
- nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
- nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
- nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
- nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
- nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
- nautobot/ipam/migrations/0013_delete_role.py +0 -1
- nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
- nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
- nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
- nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
- nautobot/ipam/migrations/0019_created_datetime.py +0 -1
- nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
- nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
- nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
- nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
- nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
- nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
- nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
- nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
- nautobot/ipam/migrations/0028_tagsfield.py +44 -0
- nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
- nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
- nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
- nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
- nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
- nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
- nautobot/ipam/models.py +579 -368
- nautobot/ipam/navigation.py +36 -159
- nautobot/ipam/querysets.py +117 -90
- nautobot/ipam/signals.py +89 -0
- nautobot/ipam/tables.py +86 -28
- nautobot/ipam/templates/ipam/ipaddress.html +14 -30
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
- nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
- nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
- nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
- nautobot/ipam/templates/ipam/prefix.html +27 -33
- nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
- nautobot/ipam/templates/ipam/vlangroup.html +0 -13
- nautobot/ipam/templates/ipam/vrf.html +6 -4
- nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
- nautobot/ipam/tests/integration/test_prefixes.py +4 -27
- nautobot/ipam/tests/test_api.py +60 -61
- nautobot/ipam/tests/test_filters.py +187 -126
- nautobot/ipam/tests/test_forms.py +12 -6
- nautobot/ipam/tests/test_graphql.py +8 -6
- nautobot/ipam/tests/test_migrations.py +8 -13
- nautobot/ipam/tests/test_models.py +426 -274
- nautobot/ipam/tests/test_ordering.py +6 -3
- nautobot/ipam/tests/test_querysets.py +340 -96
- nautobot/ipam/tests/test_views.py +100 -55
- nautobot/ipam/urls.py +28 -5
- nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
- nautobot/ipam/utils/migrations.py +713 -0
- nautobot/ipam/views.py +237 -122
- nautobot/project-static/docs/404.html +1399 -166
- nautobot/project-static/docs/additional-features/caching.html +1416 -320
- nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
- nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
- nautobot/project-static/docs/additional-features/graphql.html +1389 -190
- nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
- nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
- nautobot/project-static/docs/additional-features/jobs.html +1677 -460
- nautobot/project-static/docs/additional-features/napalm.html +1389 -190
- nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
- nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
- nautobot/project-static/docs/administration/celery-queues.html +1389 -190
- nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
- nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
- nautobot/project-static/docs/administration/permissions.html +1389 -190
- nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
- nautobot/project-static/docs/apps/index.html +1389 -190
- nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
- nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
- nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
- nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
- nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
- nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
- nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
- nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
- nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
- nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
- nautobot/project-static/docs/configuration/index.html +1398 -199
- nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
- nautobot/project-static/docs/configuration/required-settings.html +1419 -287
- nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
- nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
- nautobot/project-static/docs/core-functionality/devices.html +1452 -249
- nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
- nautobot/project-static/docs/core-functionality/power.html +1448 -249
- nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
- nautobot/project-static/docs/core-functionality/services.html +1448 -249
- nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
- nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
- nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
- nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
- nautobot/project-static/docs/development/application-registry.html +1393 -214
- nautobot/project-static/docs/development/best-practices.html +1392 -192
- nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
- nautobot/project-static/docs/development/extending-models.html +1443 -257
- nautobot/project-static/docs/development/generic-views.html +1403 -174
- nautobot/project-static/docs/development/getting-started.html +1568 -262
- nautobot/project-static/docs/development/homepage.html +1389 -190
- nautobot/project-static/docs/development/index.html +1389 -190
- nautobot/project-static/docs/development/model-features.html +1389 -190
- nautobot/project-static/docs/development/natural-keys.html +1389 -190
- nautobot/project-static/docs/development/navigation-menu.html +1451 -330
- nautobot/project-static/docs/development/react-ui.html +4199 -0
- nautobot/project-static/docs/development/release-checklist.html +1392 -193
- nautobot/project-static/docs/development/role-internals.html +1402 -172
- nautobot/project-static/docs/development/style-guide.html +1399 -199
- nautobot/project-static/docs/development/templates.html +1391 -191
- nautobot/project-static/docs/development/testing.html +1389 -190
- nautobot/project-static/docs/development/user-preferences.html +1389 -190
- nautobot/project-static/docs/docker/index.html +1408 -206
- nautobot/project-static/docs/index.html +1397 -180
- nautobot/project-static/docs/installation/centos.html +1401 -170
- nautobot/project-static/docs/installation/external-authentication.html +1389 -190
- nautobot/project-static/docs/installation/http-server.html +1389 -190
- nautobot/project-static/docs/installation/index.html +1394 -191
- nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
- nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
- nautobot/project-static/docs/installation/nautobot.html +1390 -191
- nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
- nautobot/project-static/docs/installation/services.html +1389 -190
- nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/project-static/docs/installation/ubuntu.html +1401 -170
- nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
- nautobot/project-static/docs/installation/upgrading.html +1395 -192
- nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
- nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
- nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
- nautobot/project-static/docs/models/circuits/provider.html +1427 -174
- nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
- nautobot/project-static/docs/models/dcim/cable.html +1458 -174
- nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/device.html +1431 -174
- nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
- nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
- nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
- nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
- nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/interface.html +1427 -174
- nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
- nautobot/project-static/docs/models/dcim/location.html +1427 -174
- nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
- nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
- nautobot/project-static/docs/models/dcim/platform.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
- nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
- nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
- nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/rack.html +1427 -174
- nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
- nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
- nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
- nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/region.html +1401 -170
- nautobot/project-static/docs/models/dcim/site.html +1401 -170
- nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
- nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
- nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
- nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
- nautobot/project-static/docs/models/extras/customfield.html +1389 -190
- nautobot/project-static/docs/models/extras/customlink.html +1389 -190
- nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
- nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
- nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
- nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
- nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
- nautobot/project-static/docs/models/extras/job.html +1411 -157
- nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
- nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
- nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
- nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
- nautobot/project-static/docs/models/extras/note.html +1389 -190
- nautobot/project-static/docs/models/extras/relationship.html +1391 -192
- nautobot/project-static/docs/models/extras/role.html +1495 -198
- nautobot/project-static/docs/models/extras/secret.html +1492 -201
- nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
- nautobot/project-static/docs/models/extras/status.html +1381 -221
- nautobot/project-static/docs/models/extras/tag.html +1389 -190
- nautobot/project-static/docs/models/extras/webhook.html +1389 -190
- nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
- nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
- nautobot/project-static/docs/models/ipam/rir.html +1410 -157
- nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
- nautobot/project-static/docs/models/ipam/service.html +1410 -157
- nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
- nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
- nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
- nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
- nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
- nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
- nautobot/project-static/docs/models/users/token.html +1410 -157
- nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
- nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
- nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
- nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
- nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/plugins/development.html +1916 -646
- nautobot/project-static/docs/plugins/index.html +1389 -190
- nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
- nautobot/project-static/docs/release-notes/index.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
- nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
- nautobot/project-static/docs/requirements.txt +5 -4
- nautobot/project-static/docs/rest-api/authentication.html +1389 -190
- nautobot/project-static/docs/rest-api/filtering.html +1389 -190
- nautobot/project-static/docs/rest-api/overview.html +2002 -576
- nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +197 -187
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
- nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
- nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
- nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
- nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
- nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
- nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
- nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
- nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
- nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
- nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
- nautobot/project-static/docs/user-guides/graphql.html +1402 -203
- nautobot/project-static/docs/user-guides/relationships.html +1448 -249
- nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
- nautobot/project-static/js/forms.js +16 -9
- nautobot/project-static/js/theme.js +5 -0
- nautobot/tenancy/api/serializers.py +4 -34
- nautobot/tenancy/api/urls.py +1 -1
- nautobot/tenancy/filters/__init__.py +9 -7
- nautobot/tenancy/filters/mixins.py +3 -2
- nautobot/tenancy/forms.py +3 -36
- nautobot/tenancy/migrations/0001_initial.py +0 -1
- nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
- nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
- nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
- nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
- nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
- nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
- nautobot/tenancy/models.py +0 -30
- nautobot/tenancy/navigation.py +6 -39
- nautobot/tenancy/tables.py +4 -4
- nautobot/tenancy/templates/tenancy/tenant.html +12 -12
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/tests/test_api.py +1 -12
- nautobot/tenancy/tests/test_filters.py +20 -12
- nautobot/tenancy/tests/test_views.py +11 -29
- nautobot/tenancy/urls.py +10 -10
- nautobot/tenancy/views.py +0 -3
- nautobot/ui/.eslintignore +6 -0
- nautobot/ui/.gitignore +10 -0
- nautobot/ui/.prettierignore +9 -0
- nautobot/ui/.prettierrc +4 -0
- nautobot/ui/README.md +33 -0
- nautobot/ui/app_imports.js.j2 +7 -0
- nautobot/ui/craco.config.js +46 -0
- nautobot/ui/jsconfig-base.json +11 -0
- nautobot/ui/jsconfig.json +5 -0
- nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
- nautobot/ui/package-lock.json +21451 -0
- nautobot/ui/package.json +70 -0
- nautobot/ui/public/index.html +47 -0
- nautobot/ui/public/logo192.png +0 -0
- nautobot/ui/public/logo512.png +0 -0
- nautobot/ui/public/manifest.json +25 -0
- nautobot/ui/public/nautobot_logo.svg +131 -0
- nautobot/ui/public/robots.txt +3 -0
- nautobot/ui/src/App.js +71 -0
- nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
- nautobot/ui/src/components/AppTab.js +40 -0
- nautobot/ui/src/components/Apps.js +60 -0
- nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
- nautobot/ui/src/components/HomePanel.js +58 -0
- nautobot/ui/src/components/JobHistoryTable.js +78 -0
- nautobot/ui/src/components/Layout.js +53 -0
- nautobot/ui/src/components/LoadingWidget.js +25 -0
- nautobot/ui/src/components/Navbar.js +116 -0
- nautobot/ui/src/components/NotificationPopover.js +27 -0
- nautobot/ui/src/components/ObjectListTable.js +209 -0
- nautobot/ui/src/components/ReferenceDataTag.js +35 -0
- nautobot/ui/src/components/RouterButton.js +10 -0
- nautobot/ui/src/components/RouterLink.js +10 -0
- nautobot/ui/src/components/SidebarNav.js +147 -0
- nautobot/ui/src/components/Table.js +48 -0
- nautobot/ui/src/components/TableItem.js +71 -0
- nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
- nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
- nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
- nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
- nautobot/ui/src/components/__tests__/Table.test.js +36 -0
- nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
- nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
- nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
- nautobot/ui/src/components/pagination.js +93 -0
- nautobot/ui/src/components/paginator.js +79 -0
- nautobot/ui/src/components/paginator_form.js +43 -0
- nautobot/ui/src/components/usePagination.js +57 -0
- nautobot/ui/src/constants/apiPath.js +10 -0
- nautobot/ui/src/constants/icons.js +15 -0
- nautobot/ui/src/constants/size.js +15 -0
- nautobot/ui/src/index.js +65 -0
- nautobot/ui/src/reportWebVitals.js +15 -0
- nautobot/ui/src/router.js +77 -0
- nautobot/ui/src/utils/api.js +131 -0
- nautobot/ui/src/utils/app-import.js +15 -0
- nautobot/ui/src/utils/color.js +15 -0
- nautobot/ui/src/utils/date.js +14 -0
- nautobot/ui/src/utils/index.js +15 -0
- nautobot/ui/src/utils/navigation.js +32 -0
- nautobot/ui/src/utils/session.js +64 -0
- nautobot/ui/src/utils/store.js +242 -0
- nautobot/ui/src/utils/string.js +6 -0
- nautobot/ui/src/utils/url.js +4 -0
- nautobot/ui/src/views/Home.js +138 -0
- nautobot/ui/src/views/InstalledApps.js +80 -0
- nautobot/ui/src/views/Login.js +48 -0
- nautobot/ui/src/views/Logout.js +20 -0
- nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
- nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
- nautobot/ui/src/views/__tests__/Login.test.js +15 -0
- nautobot/ui/src/views/generic/GenericView.js +142 -0
- nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
- nautobot/ui/src/views/generic/ObjectList.js +127 -0
- nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +51 -61
- nautobot/users/api/urls.py +1 -1
- nautobot/users/api/views.py +53 -2
- nautobot/users/migrations/0001_initial.py +0 -1
- nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
- nautobot/users/migrations/0003_alter_user_options.py +0 -1
- nautobot/users/migrations/0004_alter_user_managers.py +0 -1
- nautobot/users/tests/test_api.py +109 -28
- nautobot/users/tests/test_filters.py +0 -4
- nautobot/users/tests/test_models.py +0 -1
- nautobot/users/views.py +0 -7
- nautobot/virtualization/api/serializers.py +18 -132
- nautobot/virtualization/api/urls.py +1 -1
- nautobot/virtualization/api/views.py +1 -22
- nautobot/virtualization/choices.py +0 -2
- nautobot/virtualization/filters.py +12 -7
- nautobot/virtualization/forms.py +21 -117
- nautobot/virtualization/migrations/0001_initial.py +0 -1
- nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
- nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
- nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
- nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
- nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
- nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
- nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
- nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
- nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
- nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
- nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
- nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
- nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
- nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
- nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
- nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
- nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
- nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
- nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
- nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
- nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
- nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
- nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
- nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
- nautobot/virtualization/models.py +39 -131
- nautobot/virtualization/navigation.py +18 -99
- nautobot/virtualization/tables.py +4 -4
- nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
- nautobot/virtualization/tests/test_api.py +42 -52
- nautobot/virtualization/tests/test_filters.py +98 -75
- nautobot/virtualization/tests/test_models.py +36 -13
- nautobot/virtualization/tests/test_views.py +68 -73
- nautobot/virtualization/urls.py +10 -10
- nautobot/virtualization/views.py +8 -14
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
- nautobot/circuits/api/nested_serializers.py +0 -69
- nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
- nautobot/dcim/api/nested_serializers.py +0 -356
- nautobot/dcim/templates/dcim/device_import.html +0 -5
- nautobot/dcim/templates/dcim/device_import_child.html +0 -5
- nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
- nautobot/extras/api/nested_serializers.py +0 -353
- nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
- nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
- nautobot/extras/reports.py +0 -60
- nautobot/extras/scripts.py +0 -72
- nautobot/extras/tests/example_jobs/script_variables.py +0 -67
- nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
- nautobot/extras/tests/example_jobs/test_fail.py +0 -16
- nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
- nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
- nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
- nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
- nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
- nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
- nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
- nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
- nautobot/extras/tests/example_jobs/test_pass.py +0 -19
- nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
- nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
- nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
- nautobot/ipam/api/nested_serializers.py +0 -143
- nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
- nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
- nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
- nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
- nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
- nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
- nautobot/tenancy/api/nested_serializers.py +0 -31
- nautobot/users/api/nested_serializers.py +0 -67
- nautobot/virtualization/api/nested_serializers.py +0 -65
- /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
- /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
nautobot/core/api/fields.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
|
-
|
|
3
2
|
from django.core.exceptions import ObjectDoesNotExist
|
|
4
3
|
from drf_spectacular.utils import extend_schema_field
|
|
5
4
|
from rest_framework import serializers
|
|
@@ -8,6 +7,7 @@ from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
|
|
|
8
7
|
from timezone_field.rest_framework import TimeZoneSerializerField as TimeZoneSerializerField_
|
|
9
8
|
|
|
10
9
|
|
|
10
|
+
# TODO: why is this not a serializers.ChoiceField subclass??
|
|
11
11
|
class ChoiceField(serializers.Field):
|
|
12
12
|
"""
|
|
13
13
|
Represent a ChoiceField as {'value': <DB value>, 'label': <string>}. Accepts a single value on write.
|
|
@@ -51,9 +51,18 @@ class ChoiceField(serializers.Field):
|
|
|
51
51
|
return data
|
|
52
52
|
raise ValidationError("This field may not be blank.")
|
|
53
53
|
|
|
54
|
+
if isinstance(data, dict):
|
|
55
|
+
if "value" in data:
|
|
56
|
+
data = data["value"]
|
|
57
|
+
else:
|
|
58
|
+
raise ValidationError(
|
|
59
|
+
'Value must be passed directly (e.g. "foo": 123) '
|
|
60
|
+
'or as a dict with key "value" (e.g. "foo": {"value": 123}).'
|
|
61
|
+
)
|
|
62
|
+
|
|
54
63
|
# Provide an explicit error message if the request is trying to write a dict or list
|
|
55
|
-
if isinstance(data,
|
|
56
|
-
raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a
|
|
64
|
+
if isinstance(data, list):
|
|
65
|
+
raise ValidationError('Value must be passed directly (e.g. "foo": 123); do not use a list.')
|
|
57
66
|
|
|
58
67
|
# Check for string representations of boolean/integer values
|
|
59
68
|
if hasattr(data, "lower"):
|
|
@@ -110,9 +119,9 @@ class ObjectTypeField(serializers.CharField):
|
|
|
110
119
|
Represent the ContentType of this serializer's model as "<app_label>.<model>".
|
|
111
120
|
"""
|
|
112
121
|
|
|
113
|
-
def __init__(self, *args, read_only=True, **kwargs): # pylint: disable=useless-parent-delegation
|
|
122
|
+
def __init__(self, *args, read_only=True, source="*", **kwargs): # pylint: disable=useless-parent-delegation
|
|
114
123
|
"""Default read_only to True as this should never be a writable field."""
|
|
115
|
-
super().__init__(*args, read_only=read_only, **kwargs)
|
|
124
|
+
super().__init__(*args, read_only=read_only, source=source, **kwargs)
|
|
116
125
|
|
|
117
126
|
def to_representation(self, _value):
|
|
118
127
|
"""
|
|
@@ -121,7 +130,7 @@ class ObjectTypeField(serializers.CharField):
|
|
|
121
130
|
Implemented this way because `_value` may be None when generating the schema.
|
|
122
131
|
"""
|
|
123
132
|
model = self.parent.Meta.model
|
|
124
|
-
return
|
|
133
|
+
return model._meta.label_lower
|
|
125
134
|
|
|
126
135
|
|
|
127
136
|
class SerializedPKRelatedField(PrimaryKeyRelatedField):
|
|
@@ -17,11 +17,12 @@ class NautobotFilterBackend(DjangoFilterBackend):
|
|
|
17
17
|
|
|
18
18
|
for non_filter_param in (
|
|
19
19
|
"api_version", # used to select the Nautobot API version
|
|
20
|
-
"
|
|
20
|
+
"depth", # nested levels of the serializers default to depth=0
|
|
21
21
|
"format", # "json" or "api", used in the interactive HTML REST API views
|
|
22
|
-
"include", # used to include computed fields (excluded by default)
|
|
22
|
+
"include", # used to include computed fields, relationships, config-contexts, etc. (excluded by default)
|
|
23
23
|
"limit", # pagination
|
|
24
24
|
"offset", # pagination
|
|
25
|
+
"sort", # sorting of results
|
|
25
26
|
):
|
|
26
27
|
data.pop(non_filter_param, None)
|
|
27
28
|
|
nautobot/core/api/metadata.py
CHANGED
|
@@ -1,21 +1,180 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
1
3
|
from django.core.exceptions import PermissionDenied
|
|
2
4
|
from django.http import Http404
|
|
3
|
-
|
|
5
|
+
import drf_react_template.schema_form_encoder as schema
|
|
4
6
|
from rest_framework import exceptions
|
|
7
|
+
from rest_framework import fields as drf_fields
|
|
8
|
+
from rest_framework import serializers as drf_serializers
|
|
5
9
|
from rest_framework.metadata import SimpleMetadata
|
|
6
10
|
from rest_framework.request import clone_request
|
|
7
11
|
|
|
8
|
-
from nautobot.core.api import ContentTypeField
|
|
9
12
|
|
|
13
|
+
# FIXME(jathan): I hate this pattern that these fields are hard-coded here. But for the moment, this
|
|
14
|
+
# works reliably.
|
|
15
|
+
BOTTOM_FIELDS = ["computed_fields", "custom_fields", "relationships"]
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
|
|
18
|
+
class NautobotProcessingMixin(schema.ProcessingMixin):
|
|
19
|
+
"""Processing mixin to account for custom field types and behaviors for Nautobot."""
|
|
20
|
+
|
|
21
|
+
def _get_type_map_value(self, field: schema.SerializerType):
|
|
22
|
+
"""Overload default to add "required" as a default mapping."""
|
|
23
|
+
# This adds "required" as a default type mapping compared to drf_react_template core.
|
|
24
|
+
result = {
|
|
25
|
+
"type": field.style.get("schema:type"),
|
|
26
|
+
"enum": field.style.get("schema:enum"),
|
|
27
|
+
"format": field.style.get("schema:format"),
|
|
28
|
+
"widget": field.style.get("ui:widget"),
|
|
29
|
+
"required": field.style.get("schema:required"),
|
|
30
|
+
"readOnly": field.style.get("schema:readOnly"),
|
|
31
|
+
}
|
|
32
|
+
result_default = self.TYPE_MAP.get(type(field).__name__, {})
|
|
33
|
+
for k in result_default:
|
|
34
|
+
if result[k] is None:
|
|
35
|
+
result[k] = result_default[k]
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def order_fields(self, fields):
|
|
39
|
+
"""Explicitly order the "big ugly" fields to the bottom."""
|
|
40
|
+
# FIXME(jathan): Correct the behavior introduced in #3500 by switching to `__all__` to
|
|
41
|
+
# assert these get added at the end.
|
|
42
|
+
for field_name in BOTTOM_FIELDS:
|
|
43
|
+
if field_name in fields:
|
|
44
|
+
fields.remove(field_name)
|
|
45
|
+
fields.append(field_name)
|
|
46
|
+
return fields
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class NautobotSchemaProcessor(NautobotProcessingMixin, schema.SchemaProcessor):
|
|
50
|
+
"""SchemaProcessor to account for custom field types and behaviors for Nautobot."""
|
|
51
|
+
|
|
52
|
+
def _get_field_properties(self, field: schema.SerializerType, name: str) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
This method is used to generate the proper schema based on serializer field mappings or
|
|
55
|
+
per-field attribute markup.
|
|
56
|
+
|
|
57
|
+
This has been overloaded with an `elif` to account for `ManyRelatedField`.
|
|
58
|
+
"""
|
|
59
|
+
result = {}
|
|
60
|
+
type_map_obj = self._get_type_map_value(field)
|
|
61
|
+
result["type"] = type_map_obj["type"]
|
|
62
|
+
result["title"] = self._get_title(field, name)
|
|
63
|
+
|
|
64
|
+
if isinstance(field, drf_serializers.ListField):
|
|
65
|
+
if field.allow_empty:
|
|
66
|
+
result["required"] = not getattr(field, "allow_empty", True)
|
|
67
|
+
result["items"] = self._get_field_properties(field.child, "")
|
|
68
|
+
result["uniqueItems"] = True
|
|
69
|
+
elif isinstance(field, drf_serializers.ManyRelatedField):
|
|
70
|
+
if field.allow_empty:
|
|
71
|
+
result["required"] = type_map_obj.get("required", [])
|
|
72
|
+
result["items"] = self._get_field_properties(field.child_relation, "")
|
|
73
|
+
result["uniqueItems"] = True
|
|
74
|
+
else:
|
|
75
|
+
if field.allow_null:
|
|
76
|
+
result["type"] = [result["type"], "null"]
|
|
77
|
+
enum = type_map_obj.get("enum")
|
|
78
|
+
if enum:
|
|
79
|
+
if enum == "choices":
|
|
80
|
+
choices = field.choices
|
|
81
|
+
result["enum"] = list(choices.keys())
|
|
82
|
+
result["enumNames"] = list(choices.values())
|
|
83
|
+
if isinstance(enum, (list, tuple)):
|
|
84
|
+
if isinstance(enum, (list, tuple)):
|
|
85
|
+
result["enum"] = [item[0] for item in enum]
|
|
86
|
+
result["enumNames"] = [item[1] for item in enum]
|
|
87
|
+
else:
|
|
88
|
+
result["enum"] = enum
|
|
89
|
+
result["enumNames"] = list(enum)
|
|
90
|
+
|
|
91
|
+
# Process "format"
|
|
92
|
+
format_ = type_map_obj["format"]
|
|
93
|
+
if format_:
|
|
94
|
+
result["format"] = format_
|
|
95
|
+
|
|
96
|
+
# Process "readOnly"
|
|
97
|
+
read_only = type_map_obj["readOnly"]
|
|
98
|
+
if read_only:
|
|
99
|
+
result["readOnly"] = read_only
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
result["default"] = field.get_default()
|
|
103
|
+
except drf_fields.SkipField:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
result = self._set_validation_properties(field, result)
|
|
107
|
+
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class NautobotUiSchemaProcessor(NautobotProcessingMixin, schema.UiSchemaProcessor):
|
|
112
|
+
"""UiSchemaProcessor to account for custom field types and behaviors for Nautobot."""
|
|
113
|
+
|
|
114
|
+
def _field_order(self) -> List[str]:
|
|
115
|
+
"""
|
|
116
|
+
Overload the base which just returns `Meta.fields` and doesn't play nicely with "__all__".
|
|
117
|
+
|
|
118
|
+
This instead calls `get_fields()` and returns the keys.
|
|
119
|
+
"""
|
|
120
|
+
if self._is_list_serializer(self.serializer):
|
|
121
|
+
fields = self.serializer.child.get_fields()
|
|
122
|
+
else:
|
|
123
|
+
fields = self.serializer.get_fields()
|
|
124
|
+
|
|
125
|
+
field_names = self.order_fields(list(fields))
|
|
126
|
+
|
|
127
|
+
return field_names
|
|
128
|
+
|
|
129
|
+
def _get_ui_field_properties(self, field: schema.SerializerType, name: str) -> Dict[str, Any]:
|
|
13
130
|
"""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
with bulk update in place (see #5470).
|
|
131
|
+
We had to overload this here to make it so that array types with children validate properly
|
|
132
|
+
and to also use `NautobotUiSchemaProcessor` over the default.
|
|
17
133
|
"""
|
|
18
|
-
|
|
134
|
+
data_index = self._generate_data_index(name)
|
|
135
|
+
result = {}
|
|
136
|
+
is_list = False
|
|
137
|
+
if self._is_field_serializer(field):
|
|
138
|
+
return NautobotUiSchemaProcessor(field, self.renderer_context, prefix=data_index).get_ui_schema()
|
|
139
|
+
elif isinstance(field, drf_serializers.ListField):
|
|
140
|
+
is_list = True
|
|
141
|
+
child = field.child
|
|
142
|
+
is_int = isinstance(child, drf_serializers.IntegerField)
|
|
143
|
+
widget = self._get_type_map_value(field=child).get("widget")
|
|
144
|
+
if not widget and isinstance(child, drf_serializers.ChoiceField):
|
|
145
|
+
widget = "checkbox"
|
|
146
|
+
else:
|
|
147
|
+
widget = self._get_type_map_value(field=field).get("widget")
|
|
148
|
+
help_text = field.help_text
|
|
149
|
+
if widget:
|
|
150
|
+
if is_list and is_int:
|
|
151
|
+
if "items" not in result:
|
|
152
|
+
result["items"] = {}
|
|
153
|
+
result["items"]["ui:widget"] = widget
|
|
154
|
+
else:
|
|
155
|
+
result["ui:widget"] = widget
|
|
156
|
+
if help_text:
|
|
157
|
+
result["ui:help"] = help_text
|
|
158
|
+
result.update(self._get_style_dict(field))
|
|
159
|
+
result = self._set_validation_properties(field, result)
|
|
160
|
+
return result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class NautobotColumnProcessor(NautobotProcessingMixin, schema.ColumnProcessor):
|
|
164
|
+
"""ColumnProcessor to account for custom field types and behaviors for Nautobot."""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class NautobotMetadata(SimpleMetadata):
|
|
168
|
+
"""
|
|
169
|
+
Metadata class that emits JSON schema. It contains `schema` and `uiSchema` keys where:
|
|
170
|
+
|
|
171
|
+
- schema: The object JSON schema
|
|
172
|
+
- uiSchema: The object UI schema which describes the form layout in the UI
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def determine_actions(self, request, view):
|
|
176
|
+
"""Generate the actions and return the names of the allowed methods."""
|
|
177
|
+
actions = []
|
|
19
178
|
for method in {"PUT", "POST"} & set(view.allowed_methods):
|
|
20
179
|
view.request = clone_request(request, method)
|
|
21
180
|
try:
|
|
@@ -28,32 +187,80 @@ class NautobotMetadata(SimpleMetadata):
|
|
|
28
187
|
except (exceptions.APIException, PermissionDenied, Http404):
|
|
29
188
|
pass
|
|
30
189
|
else:
|
|
31
|
-
|
|
32
|
-
# appropriate metadata about the fields that should be supplied.
|
|
33
|
-
serializer = view.get_serializer()
|
|
34
|
-
actions[method] = self.get_serializer_info(serializer)
|
|
190
|
+
actions.append(method)
|
|
35
191
|
finally:
|
|
36
192
|
view.request = request
|
|
37
|
-
|
|
38
193
|
return actions
|
|
39
194
|
|
|
40
|
-
def
|
|
41
|
-
"""
|
|
42
|
-
|
|
195
|
+
def get_list_display_fields(self, serializer):
|
|
196
|
+
"""Try to get the list display fields or default to an empty list."""
|
|
197
|
+
serializer_meta = getattr(serializer, "Meta", None)
|
|
198
|
+
return list(getattr(serializer_meta, "list_display_fields", []))
|
|
43
199
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
200
|
+
def determine_view_options(self, request, serializer):
|
|
201
|
+
"""Determine view options that will be used for non-form display metadata."""
|
|
202
|
+
view_options = {}
|
|
203
|
+
list_display = []
|
|
204
|
+
fields = []
|
|
205
|
+
|
|
206
|
+
processor = NautobotColumnProcessor(serializer, request.parser_context)
|
|
207
|
+
field_map = dict(serializer.fields)
|
|
208
|
+
all_fields = list(field_map)
|
|
209
|
+
|
|
210
|
+
# Explicitly order the "big ugly" fields to the bottom.
|
|
211
|
+
processor.order_fields(all_fields)
|
|
212
|
+
|
|
213
|
+
list_display_fields = self.get_list_display_fields(serializer)
|
|
214
|
+
|
|
215
|
+
# Process the list_display fields first.
|
|
216
|
+
for field_name in list_display_fields:
|
|
217
|
+
try:
|
|
218
|
+
field = field_map[field_name]
|
|
219
|
+
except KeyError:
|
|
220
|
+
continue # Ignore unknown fields.
|
|
221
|
+
column_data = processor._get_column_properties(field, field_name)
|
|
222
|
+
list_display.append(column_data)
|
|
223
|
+
fields.append(column_data)
|
|
224
|
+
|
|
225
|
+
# Process the rest of the fields second.
|
|
226
|
+
for field_name in all_fields:
|
|
227
|
+
# Don't process list display fields twice.
|
|
228
|
+
if field_name in list_display_fields:
|
|
229
|
+
continue
|
|
230
|
+
try:
|
|
231
|
+
field = field_map[field_name]
|
|
232
|
+
except KeyError:
|
|
233
|
+
continue # Ignore unknown fields.
|
|
234
|
+
column_data = processor._get_column_properties(field, field_name)
|
|
235
|
+
fields.append(column_data)
|
|
236
|
+
|
|
237
|
+
view_options["list_display_fields"] = list_display
|
|
238
|
+
view_options["fields"] = fields
|
|
239
|
+
|
|
240
|
+
return view_options
|
|
241
|
+
|
|
242
|
+
def determine_metadata(self, request, view):
|
|
243
|
+
"""This is the metadata that gets returned on an `OPTIONS` request."""
|
|
244
|
+
metadata = super().determine_metadata(request, view)
|
|
245
|
+
|
|
246
|
+
# If there's a serializer, do the needful to bind the schema/uiSchema.
|
|
247
|
+
if hasattr(view, "get_serializer"):
|
|
248
|
+
serializer = view.get_serializer()
|
|
249
|
+
# TODO(jathan): Bit of a WIP here. Will likely refactor. There might be cases where we
|
|
250
|
+
# want to explicitly override the UI field ordering, but that's not yet accounted for
|
|
251
|
+
# here. For now the assertion is always put the `list_display_fields` first, and then
|
|
252
|
+
# include the rest in whatever order.
|
|
253
|
+
# See: https://rjsf-team.github.io/react-jsonschema-form/docs/usage/objects#specifying-property-order
|
|
254
|
+
ui_schema = NautobotUiSchemaProcessor(serializer, request.parser_context).get_ui_schema()
|
|
255
|
+
ui_schema["ui:order"] = self.get_list_display_fields(serializer) + ["*"]
|
|
256
|
+
metadata.update(
|
|
52
257
|
{
|
|
53
|
-
"
|
|
54
|
-
"
|
|
258
|
+
"schema": NautobotSchemaProcessor(serializer, request.parser_context).get_schema(),
|
|
259
|
+
# "uiSchema": NautobotUiSchemaProcessor(serializer, request.parser_context).get_ui_schema(),
|
|
260
|
+
"uiSchema": ui_schema,
|
|
55
261
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
metadata["view_options"] = self.determine_view_options(request, serializer)
|
|
265
|
+
|
|
266
|
+
return metadata
|
nautobot/core/api/mixins.py
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from django.core.exceptions import (
|
|
5
|
+
FieldError,
|
|
6
|
+
MultipleObjectsReturned,
|
|
7
|
+
ObjectDoesNotExist,
|
|
8
|
+
)
|
|
9
|
+
from django.db.models import AutoField
|
|
10
|
+
from rest_framework.exceptions import ValidationError
|
|
11
|
+
|
|
12
|
+
from nautobot.core.api.utils import dict_to_filter_params
|
|
13
|
+
from nautobot.core.utils.data import is_url
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
1
18
|
class LimitQuerysetChoicesSerializerMixin:
|
|
2
19
|
"""Mixin field that restricts queryset choices to those accessible
|
|
3
20
|
for the queryset model that implemented it."""
|
|
@@ -12,3 +29,80 @@ class LimitQuerysetChoicesSerializerMixin:
|
|
|
12
29
|
except AttributeError:
|
|
13
30
|
model = self.parent.parent.Meta.model
|
|
14
31
|
return queryset.get_for_model(model)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WritableSerializerMixin:
|
|
35
|
+
"""
|
|
36
|
+
WritableSerializerMixin provides the to_internal_value() function.
|
|
37
|
+
The function provides the ability to write API requests that identify unique objects based on
|
|
38
|
+
combinations of fields other than the primary key.
|
|
39
|
+
e.g:
|
|
40
|
+
"parent": { "location_type__parent": {"name": "Campus"}, "parent__name": "Campus-29" }
|
|
41
|
+
vs
|
|
42
|
+
"parent": "10dff139-7333-46b0-bef6-f6a5a7b5497c"
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def remove_non_filter_fields(self, filter_params):
|
|
46
|
+
"""
|
|
47
|
+
Make output from a WritableSerializer "round-trip" capable by automatically stripping from the
|
|
48
|
+
data any serializer fields that do not correspond to a specific model field
|
|
49
|
+
"""
|
|
50
|
+
if hasattr(self, "fields"):
|
|
51
|
+
for field_name, field_instance in self.fields.items():
|
|
52
|
+
if field_name in filter_params and field_instance.source == "*":
|
|
53
|
+
logger.debug("Discarding non-filter field %s", field_name)
|
|
54
|
+
del filter_params[field_name]
|
|
55
|
+
return filter_params
|
|
56
|
+
|
|
57
|
+
def get_queryset_filter_params(self, data, queryset):
|
|
58
|
+
"""
|
|
59
|
+
Data could be a dictionary and an int (for the User model) or a str that represents the primary key.
|
|
60
|
+
If it is a dictionary, we return it after remove non-filter fields.
|
|
61
|
+
If it is a primary key, we return a dictionary object formatted like this {"pk": pk}
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(data, dict):
|
|
64
|
+
params = dict_to_filter_params(data)
|
|
65
|
+
return self.remove_non_filter_fields(params)
|
|
66
|
+
|
|
67
|
+
# Account for the fact that HyperlinkedIdentityFields might pass in URLs.
|
|
68
|
+
if is_url(data):
|
|
69
|
+
# Strip the trailing slash and split on slashes, taking the last value as the PK.
|
|
70
|
+
data = data.rstrip("/").split("/")[-1]
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
# The int case here is taking into account for the User model we inherit from django
|
|
74
|
+
pk = int(data) if isinstance(queryset.model._meta.pk, AutoField) else uuid.UUID(str(data))
|
|
75
|
+
except (TypeError, ValueError) as e:
|
|
76
|
+
raise ValidationError(
|
|
77
|
+
"Related objects must be referenced by ID or by dictionary of attributes. Received an "
|
|
78
|
+
f"unrecognized value: {data}"
|
|
79
|
+
) from e
|
|
80
|
+
return {"pk": pk}
|
|
81
|
+
|
|
82
|
+
def get_object(self, data, queryset):
|
|
83
|
+
"""
|
|
84
|
+
Retrieve an unique object based on a dictionary of data attributes and raise errors accordingly if the object is not found.
|
|
85
|
+
"""
|
|
86
|
+
filter_params = self.get_queryset_filter_params(data=data, queryset=queryset)
|
|
87
|
+
try:
|
|
88
|
+
return queryset.get(**filter_params)
|
|
89
|
+
except ObjectDoesNotExist as e:
|
|
90
|
+
raise ValidationError(f"Related object not found using the provided attributes: {filter_params}") from e
|
|
91
|
+
except MultipleObjectsReturned as e:
|
|
92
|
+
raise ValidationError(f"Multiple objects match the provided attributes: {filter_params}") from e
|
|
93
|
+
except FieldError as e:
|
|
94
|
+
raise ValidationError(e) from e
|
|
95
|
+
|
|
96
|
+
def to_internal_value(self, data):
|
|
97
|
+
"""
|
|
98
|
+
Return an object or a list of objects based on a dictionary of data attributes or an UUID.
|
|
99
|
+
"""
|
|
100
|
+
if data is None:
|
|
101
|
+
return None
|
|
102
|
+
if hasattr(self, "queryset"):
|
|
103
|
+
queryset = self.queryset
|
|
104
|
+
else:
|
|
105
|
+
queryset = self.Meta.model.objects
|
|
106
|
+
if isinstance(data, list):
|
|
107
|
+
return [self.get_object(data=entry, queryset=queryset) for entry in data]
|
|
108
|
+
return self.get_object(data=data, queryset=queryset)
|
nautobot/core/api/pagination.py
CHANGED
|
@@ -11,6 +11,9 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
def paginate_queryset(self, queryset, request, view=None):
|
|
14
|
+
# No pagination when rendering to CSV
|
|
15
|
+
if "text/csv" in request.accepted_media_type:
|
|
16
|
+
return None
|
|
14
17
|
|
|
15
18
|
self.count = self.get_count(queryset)
|
|
16
19
|
self.limit = self.get_limit(request)
|
|
@@ -29,7 +32,6 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|
|
29
32
|
return list(queryset[self.offset :]) # noqa: E203
|
|
30
33
|
|
|
31
34
|
def get_limit(self, request):
|
|
32
|
-
|
|
33
35
|
if self.limit_query_param:
|
|
34
36
|
try:
|
|
35
37
|
limit = int(request.query_params[self.limit_query_param])
|
|
@@ -49,7 +51,6 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|
|
49
51
|
return get_settings_or_config("PAGINATE_COUNT")
|
|
50
52
|
|
|
51
53
|
def get_next_link(self):
|
|
52
|
-
|
|
53
54
|
# Pagination has been disabled
|
|
54
55
|
if not self.limit:
|
|
55
56
|
return None
|
|
@@ -57,7 +58,6 @@ class OptionalLimitOffsetPagination(LimitOffsetPagination):
|
|
|
57
58
|
return super().get_next_link()
|
|
58
59
|
|
|
59
60
|
def get_previous_link(self):
|
|
60
|
-
|
|
61
61
|
# Pagination has been disabled
|
|
62
62
|
if not self.limit:
|
|
63
63
|
return None
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from io import StringIO
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
|
|
8
|
+
from rest_framework import serializers
|
|
9
|
+
from rest_framework.exceptions import ParseError
|
|
10
|
+
from rest_framework.parsers import BaseParser
|
|
11
|
+
|
|
12
|
+
from nautobot.core.models.utils import deconstruct_natural_key_slug
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NautobotCSVParser(BaseParser):
|
|
19
|
+
"""Counterpart to NautobotCSVRenderer - import CSV data."""
|
|
20
|
+
|
|
21
|
+
media_type = "text/csv"
|
|
22
|
+
|
|
23
|
+
def parse(self, stream, media_type=None, parser_context=None):
|
|
24
|
+
parser_context = parser_context or {}
|
|
25
|
+
encoding = parser_context.get("encoding", "UTF-8")
|
|
26
|
+
try:
|
|
27
|
+
if "serializer_class" in parser_context:
|
|
28
|
+
# UI bulk-import case
|
|
29
|
+
serializer_class = parser_context["serializer_class"]
|
|
30
|
+
else:
|
|
31
|
+
# REST API case
|
|
32
|
+
serializer_class = parser_context["view"].get_serializer_class()
|
|
33
|
+
except (KeyError, AttributeError):
|
|
34
|
+
raise ParseError("No serializer_class was provided by the parser_context")
|
|
35
|
+
if serializer_class is None:
|
|
36
|
+
raise ParseError("Serializer class for this parser_context is None, unable to proceed")
|
|
37
|
+
|
|
38
|
+
serializer = serializer_class(context={"request": parser_context.get("request", None), "depth": 0})
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
text = stream.read().decode(encoding)
|
|
42
|
+
reader = csv.DictReader(StringIO(text))
|
|
43
|
+
|
|
44
|
+
data = []
|
|
45
|
+
for counter, row in enumerate(reader, start=1):
|
|
46
|
+
data.append(self.row_elements_to_data(counter, row, serializer=serializer))
|
|
47
|
+
|
|
48
|
+
if "pk" in parser_context.get("kwargs", {}):
|
|
49
|
+
# Single-object update, not bulk update - strip it so that we get the expected input and return format
|
|
50
|
+
data = data[0]
|
|
51
|
+
# Note that we can't distinguish between single-create and bulk-create with a list of one object,
|
|
52
|
+
# as both would have the same CSV representation. Therefore create via CSV **always** acts as bulk-create,
|
|
53
|
+
# and the response will always be a list of created objects, never a single object
|
|
54
|
+
|
|
55
|
+
if settings.DEBUG:
|
|
56
|
+
logger.debug("CSV loaded into data:\n%s", json.dumps(data, indent=2))
|
|
57
|
+
return data
|
|
58
|
+
except ParseError:
|
|
59
|
+
raise
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
raise ParseError(str(exc)) from exc
|
|
62
|
+
|
|
63
|
+
def row_elements_to_data(self, counter, row, serializer):
|
|
64
|
+
"""
|
|
65
|
+
Parse a single row of CSV data (represented as a dict) into a dict suitable for consumption by the serializer.
|
|
66
|
+
|
|
67
|
+
TODO: it would be more elegant if our serializer fields knew how to deserialize the CSV data themselves;
|
|
68
|
+
could we then literally have the parser just return list(reader) and not need this function at all?
|
|
69
|
+
"""
|
|
70
|
+
data = {}
|
|
71
|
+
for column, key in enumerate(row.keys(), start=1):
|
|
72
|
+
if not key:
|
|
73
|
+
raise ParseError(f"Row {counter}: Column {column}: missing/empty header for this column")
|
|
74
|
+
|
|
75
|
+
value = row[key]
|
|
76
|
+
if key.startswith("cf_"):
|
|
77
|
+
# Custom field
|
|
78
|
+
if value == "":
|
|
79
|
+
value = None
|
|
80
|
+
data.setdefault("custom_fields", {})[key[3:]] = value
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
serializer_field = serializer.fields.get(key, None)
|
|
84
|
+
if serializer_field is None:
|
|
85
|
+
# The REST API normally just ignores any columns the serializer doesn't understand
|
|
86
|
+
logger.debug('Skipping unknown column "%s"', key)
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if serializer_field.read_only and key != "id":
|
|
90
|
+
# Deserializing read-only fields is tricky, especially for things like SerializerMethodFields that
|
|
91
|
+
# can potentially render as anything. We don't strictly need such fields (except "id" for bulk PATCH),
|
|
92
|
+
# so let's just skip it.
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if isinstance(serializer_field, serializers.ManyRelatedField):
|
|
96
|
+
# A list of related objects, represented as a list of natural-key-slugs
|
|
97
|
+
if value:
|
|
98
|
+
related_model = serializer_field.child_relation.get_queryset().model
|
|
99
|
+
value = [self.get_natural_key_dict(slug, related_model) for slug in value.split(",")]
|
|
100
|
+
else:
|
|
101
|
+
value = []
|
|
102
|
+
elif isinstance(serializer_field, serializers.RelatedField):
|
|
103
|
+
# A single related object, represented by its natural-key-slug
|
|
104
|
+
if value:
|
|
105
|
+
related_model = serializer_field.get_queryset().model
|
|
106
|
+
value = self.get_natural_key_dict(value, related_model)
|
|
107
|
+
else:
|
|
108
|
+
value = None
|
|
109
|
+
elif isinstance(serializer_field, (serializers.ListField, serializers.MultipleChoiceField)):
|
|
110
|
+
if value:
|
|
111
|
+
value = value.split(",")
|
|
112
|
+
else:
|
|
113
|
+
value = []
|
|
114
|
+
elif isinstance(serializer_field, (serializers.DictField, serializers.JSONField)):
|
|
115
|
+
# We currently only store lists or dicts in JSONFields, never bare ints/strings.
|
|
116
|
+
# On the CSV write side, we only render dicts to JSON
|
|
117
|
+
if "{" in value or "[" in value:
|
|
118
|
+
value = json.loads(value)
|
|
119
|
+
elif value:
|
|
120
|
+
value = value.split(",")
|
|
121
|
+
try:
|
|
122
|
+
# We have some cases where it's a list of integers, such as in RackReservation.units
|
|
123
|
+
value = [int(v) for v in value]
|
|
124
|
+
except ValueError:
|
|
125
|
+
# Guess not!
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# CSV doesn't provide a ready distinction between blank and null, so in this case we have to pick one.
|
|
129
|
+
# This does mean that for a nullable AND blankable field, there's no way for CSV to set it to blank string.
|
|
130
|
+
# See corresponding logic in NautobotCSVRenderer.
|
|
131
|
+
if value == "" and serializer_field.allow_null:
|
|
132
|
+
value = None
|
|
133
|
+
|
|
134
|
+
data[key] = value
|
|
135
|
+
|
|
136
|
+
return data
|
|
137
|
+
|
|
138
|
+
def get_natural_key_dict(self, natural_key_slug, model):
|
|
139
|
+
"""
|
|
140
|
+
Get the data dictionary corresponding to the given natural key list or string for the given model.
|
|
141
|
+
"""
|
|
142
|
+
if not natural_key_slug:
|
|
143
|
+
return None
|
|
144
|
+
if model._meta.label_lower == "contenttypes.contenttype":
|
|
145
|
+
# Our ContentTypeField just uses the "app_label.model" string to look up ContentTypes, rather than the
|
|
146
|
+
# actual ([app_label, model]) natural key for ContentType.
|
|
147
|
+
return natural_key_slug
|
|
148
|
+
if model._meta.label_lower == "auth.group":
|
|
149
|
+
# auth.Group is a base Django model and so doesn't implement our natural_key_args_to_kwargs() method.
|
|
150
|
+
return {"name": deconstruct_natural_key_slug(natural_key_slug)}
|
|
151
|
+
if hasattr(model, "natural_key_args_to_kwargs"):
|
|
152
|
+
return model.natural_key_args_to_kwargs(deconstruct_natural_key_slug(natural_key_slug))
|
|
153
|
+
logger.error("%s doesn't implement natural_key_args_to_kwargs()", model.__name__)
|
|
154
|
+
return {"pk": natural_key_slug}
|