nautobot 2.0.0a3__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/apps/api.py +6 -8
- nautobot/apps/forms.py +0 -2
- nautobot/apps/ui.py +0 -8
- nautobot/circuits/api/serializers.py +9 -117
- nautobot/circuits/api/urls.py +1 -1
- nautobot/circuits/api/views.py +0 -1
- nautobot/circuits/forms.py +0 -65
- nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
- 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 -87
- nautobot/circuits/navigation.py +14 -69
- nautobot/circuits/signals.py +0 -2
- nautobot/circuits/tables.py +39 -1
- nautobot/circuits/tests/integration/test_relationships.py +9 -9
- nautobot/circuits/tests/test_api.py +4 -8
- nautobot/circuits/tests/test_filters.py +10 -4
- nautobot/circuits/tests/test_models.py +5 -1
- nautobot/circuits/tests/test_views.py +27 -5
- nautobot/circuits/views.py +18 -10
- nautobot/core/api/__init__.py +8 -2
- 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 +4 -0
- nautobot/core/api/parsers.py +154 -0
- nautobot/core/api/renderers.py +153 -2
- nautobot/core/api/schema.py +46 -2
- nautobot/core/api/serializers.py +377 -35
- 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 -72
- nautobot/core/apps/__init__.py +138 -220
- nautobot/core/celery/__init__.py +112 -41
- nautobot/core/celery/backends.py +19 -12
- 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 +0 -113
- nautobot/core/{cli.py → cli/__init__.py} +1 -1
- nautobot/core/cli/__main__.py +3 -0
- nautobot/core/constants.py +0 -24
- 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 +38 -65
- nautobot/core/forms/forms.py +4 -1
- nautobot/core/forms/utils.py +0 -52
- 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/generate_test_data.py +3 -2
- nautobot/core/management/commands/post_upgrade.py +24 -24
- nautobot/core/models/__init__.py +26 -1
- nautobot/core/models/fields.py +24 -5
- nautobot/core/models/generics.py +2 -42
- nautobot/core/models/managers.py +5 -0
- nautobot/core/models/name_color_content_types.py +0 -14
- nautobot/core/models/tree_queries.py +14 -4
- nautobot/core/models/utils.py +5 -6
- nautobot/core/models/validators.py +17 -8
- nautobot/core/releases.py +8 -10
- nautobot/core/settings.py +80 -42
- 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 +3 -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 -20
- 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 -28
- nautobot/core/testing/__init__.py +47 -44
- nautobot/core/testing/api.py +362 -47
- nautobot/core/testing/filters.py +1 -1
- nautobot/core/testing/migrations.py +2 -0
- nautobot/core/testing/mixins.py +22 -9
- nautobot/core/testing/schema.py +2 -1
- nautobot/core/testing/views.py +21 -46
- 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/test_api.py +290 -21
- nautobot/core/tests/test_checks.py +0 -7
- nautobot/core/tests/test_filters.py +107 -59
- nautobot/core/tests/test_forms.py +26 -92
- nautobot/core/tests/test_graphql.py +110 -77
- nautobot/core/tests/test_logging.py +4 -0
- nautobot/core/tests/test_managers.py +3 -1
- nautobot/core/tests/test_models.py +2 -0
- nautobot/core/tests/test_paginator.py +3 -1
- nautobot/core/tests/test_releases.py +12 -12
- nautobot/core/tests/test_templatetags_helpers.py +4 -4
- nautobot/core/tests/test_utils.py +32 -68
- nautobot/core/tests/test_views.py +12 -15
- nautobot/core/utils/data.py +17 -0
- nautobot/core/utils/deprecation.py +9 -6
- nautobot/core/utils/filtering.py +8 -3
- nautobot/core/utils/git.py +12 -4
- nautobot/core/utils/lookup.py +3 -1
- nautobot/core/utils/requests.py +1 -104
- nautobot/core/views/__init__.py +1 -0
- nautobot/core/views/generic.py +75 -110
- nautobot/core/views/mixins.py +52 -61
- nautobot/core/views/renderers.py +6 -7
- nautobot/core/views/utils.py +80 -0
- nautobot/dcim/api/serializers.py +160 -667
- nautobot/dcim/api/urls.py +1 -1
- nautobot/dcim/api/views.py +7 -44
- nautobot/dcim/choices.py +2 -0
- nautobot/dcim/filters/__init__.py +21 -0
- nautobot/dcim/form_mixins.py +1 -27
- nautobot/dcim/forms.py +19 -765
- nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
- nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
- nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
- nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
- nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
- nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
- nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
- nautobot/dcim/migrations/0040_tagsfield.py +109 -0
- nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
- nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
- nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
- nautobot/dcim/models/cables.py +3 -33
- nautobot/dcim/models/device_component_templates.py +6 -0
- nautobot/dcim/models/device_components.py +12 -198
- nautobot/dcim/models/devices.py +30 -143
- nautobot/dcim/models/locations.py +3 -64
- nautobot/dcim/models/power.py +3 -50
- nautobot/dcim/models/racks.py +7 -84
- nautobot/dcim/navigation.py +141 -467
- nautobot/dcim/signals.py +0 -2
- nautobot/dcim/tables/locations.py +2 -2
- nautobot/dcim/tables/power.py +1 -2
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/devicetype.html +2 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -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/test_api.py +166 -81
- nautobot/dcim/tests/test_cablepaths.py +41 -35
- nautobot/dcim/tests/test_filters.py +67 -23
- nautobot/dcim/tests/test_forms.py +5 -205
- nautobot/dcim/tests/test_graphql.py +7 -2
- nautobot/dcim/tests/test_migrations.py +6 -11
- nautobot/dcim/tests/test_models.py +182 -110
- nautobot/dcim/tests/test_natural_ordering.py +11 -8
- nautobot/dcim/tests/test_signals.py +6 -3
- nautobot/dcim/tests/test_views.py +197 -175
- nautobot/dcim/urls.py +11 -16
- nautobot/dcim/views.py +7 -134
- 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 +177 -195
- nautobot/docs/administration/nautobot-server.md +6 -21
- nautobot/docs/administration/replicating-nautobot.md +0 -10
- nautobot/docs/configuration/optional-settings.md +32 -41
- nautobot/docs/configuration/required-settings.md +11 -52
- nautobot/docs/development/application-registry.md +2 -13
- nautobot/docs/development/extending-models.md +15 -17
- nautobot/docs/development/generic-views.md +0 -2
- nautobot/docs/development/getting-started.md +55 -5
- nautobot/docs/development/navigation-menu.md +22 -93
- nautobot/docs/development/react-ui.md +105 -0
- nautobot/docs/development/role-internals.md +1 -3
- nautobot/docs/development/style-guide.md +6 -4
- nautobot/docs/index.md +3 -2
- nautobot/docs/installation/migrating-from-netbox.md +11 -42
- 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 +170 -747
- 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/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/virtualization/virtualmachine.md +3 -0
- nautobot/docs/plugins/development.md +83 -21
- nautobot/docs/release-notes/version-1.5.md +53 -0
- nautobot/docs/release-notes/version-2.0.md +180 -0
- nautobot/docs/requirements.txt +1 -0
- 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 -158
- nautobot/extras/api/serializers.py +165 -700
- nautobot/extras/api/urls.py +1 -1
- nautobot/extras/api/views.py +294 -280
- nautobot/extras/apps.py +4 -7
- nautobot/extras/choices.py +11 -9
- 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/filters/__init__.py +20 -19
- nautobot/extras/filters/mixins.py +4 -4
- nautobot/extras/forms/forms.py +63 -127
- nautobot/extras/forms/mixins.py +23 -51
- nautobot/extras/health_checks.py +0 -33
- nautobot/extras/jobs.py +387 -565
- nautobot/extras/management/commands/runjob.py +24 -62
- nautobot/extras/managers.py +30 -7
- 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 -1
- nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
- nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
- nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
- nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
- nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
- nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
- nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
- nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
- nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
- nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
- 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 -1
- 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 -9
- nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
- 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/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
- 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 -6
- nautobot/extras/models/jobs.py +189 -321
- nautobot/extras/models/mixins.py +0 -71
- nautobot/extras/models/models.py +0 -19
- nautobot/extras/models/relationships.py +19 -13
- nautobot/extras/models/roles.py +0 -34
- nautobot/extras/models/secrets.py +2 -26
- 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 -120
- nautobot/extras/plugins/utils.py +0 -3
- nautobot/extras/plugins/validators.py +5 -4
- nautobot/extras/plugins/views.py +16 -3
- nautobot/extras/querysets.py +1 -7
- nautobot/extras/registry.py +3 -0
- nautobot/extras/signals.py +26 -60
- nautobot/extras/tables.py +34 -40
- 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/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 +15 -17
- nautobot/extras/templates/extras/jobresult.html +24 -6
- nautobot/extras/templates/extras/scheduledjob.html +2 -2
- nautobot/extras/templates/extras/secret.html +28 -0
- nautobot/extras/templatetags/job_buttons.py +1 -0
- 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/test_computedfields.py +1 -1
- nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
- nautobot/extras/tests/integration/test_customfields.py +4 -2
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_jobs.py +25 -27
- nautobot/extras/tests/integration/test_notes.py +8 -4
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_api.py +649 -642
- nautobot/extras/tests/test_changelog.py +3 -3
- nautobot/extras/tests/test_context_managers.py +5 -3
- nautobot/extras/tests/test_customfields.py +92 -50
- nautobot/extras/tests/test_datasources.py +189 -112
- nautobot/extras/tests/test_dynamicgroups.py +7 -8
- nautobot/extras/tests/test_filters.py +137 -89
- nautobot/extras/tests/test_forms.py +73 -75
- nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
- nautobot/extras/tests/test_jobs.py +262 -263
- nautobot/extras/tests/test_migrations.py +4 -3
- nautobot/extras/tests/test_models.py +116 -161
- nautobot/extras/tests/test_plugins.py +38 -60
- nautobot/extras/tests/test_relationships.py +167 -120
- nautobot/extras/tests/test_tags.py +6 -11
- nautobot/extras/tests/test_utils.py +31 -1
- nautobot/extras/tests/test_views.py +201 -145
- nautobot/extras/tests/test_webhooks.py +6 -2
- nautobot/extras/urls.py +42 -42
- nautobot/extras/utils.py +137 -163
- nautobot/extras/views.py +78 -152
- nautobot/ipam/api/fields.py +17 -0
- nautobot/ipam/api/serializers.py +58 -164
- nautobot/ipam/api/urls.py +1 -1
- nautobot/ipam/api/views.py +3 -2
- nautobot/ipam/apps.py +1 -2
- nautobot/ipam/filters.py +1 -10
- nautobot/ipam/forms.py +4 -177
- nautobot/ipam/lookups.py +1 -0
- nautobot/ipam/management/commands/__init__.py +0 -0
- nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
- nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
- nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
- nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
- nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
- nautobot/ipam/migrations/0028_tagsfield.py +44 -0
- nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
- nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
- 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 +100 -236
- nautobot/ipam/navigation.py +36 -181
- nautobot/ipam/querysets.py +20 -25
- nautobot/ipam/signals.py +49 -6
- nautobot/ipam/tables.py +10 -3
- 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 +17 -4
- nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +0 -13
- nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
- nautobot/ipam/tests/integration/test_prefixes.py +3 -26
- nautobot/ipam/tests/test_api.py +22 -19
- nautobot/ipam/tests/test_filters.py +59 -23
- nautobot/ipam/tests/test_migrations.py +6 -10
- nautobot/ipam/tests/test_models.py +323 -198
- nautobot/ipam/tests/test_ordering.py +2 -2
- nautobot/ipam/tests/test_querysets.py +44 -24
- nautobot/ipam/tests/test_views.py +73 -26
- nautobot/ipam/urls.py +16 -0
- nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
- nautobot/ipam/utils/migrations.py +713 -0
- nautobot/ipam/views.py +137 -20
- nautobot/project-static/docs/404.html +1178 -10
- nautobot/project-static/docs/additional-features/caching.html +1224 -159
- nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
- nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
- nautobot/project-static/docs/additional-features/graphql.html +1179 -11
- nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
- nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
- nautobot/project-static/docs/additional-features/jobs.html +1514 -328
- nautobot/project-static/docs/additional-features/napalm.html +1180 -12
- nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
- nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
- nautobot/project-static/docs/administration/celery-queues.html +1178 -10
- nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
- nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
- nautobot/project-static/docs/administration/permissions.html +1178 -10
- nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
- nautobot/project-static/docs/apps/index.html +1178 -10
- nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
- nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
- nautobot/project-static/docs/configuration/index.html +1178 -10
- nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
- nautobot/project-static/docs/configuration/required-settings.html +1312 -211
- nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
- nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
- nautobot/project-static/docs/core-functionality/devices.html +1182 -10
- nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
- nautobot/project-static/docs/core-functionality/power.html +1178 -10
- nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
- nautobot/project-static/docs/core-functionality/services.html +1178 -10
- nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
- nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
- nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
- nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
- nautobot/project-static/docs/development/application-registry.html +1190 -42
- nautobot/project-static/docs/development/best-practices.html +1178 -10
- nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
- nautobot/project-static/docs/development/extending-models.html +1238 -83
- nautobot/project-static/docs/development/generic-views.html +1180 -14
- nautobot/project-static/docs/development/getting-started.html +1365 -90
- nautobot/project-static/docs/development/homepage.html +1178 -10
- nautobot/project-static/docs/development/index.html +1178 -10
- nautobot/project-static/docs/development/model-features.html +1178 -10
- nautobot/project-static/docs/development/natural-keys.html +1178 -10
- nautobot/project-static/docs/development/navigation-menu.html +1215 -125
- nautobot/project-static/docs/development/react-ui.html +4199 -0
- nautobot/project-static/docs/development/release-checklist.html +1178 -10
- nautobot/project-static/docs/development/role-internals.html +1179 -12
- nautobot/project-static/docs/development/style-guide.html +1188 -19
- nautobot/project-static/docs/development/templates.html +1178 -10
- nautobot/project-static/docs/development/testing.html +1178 -10
- nautobot/project-static/docs/development/user-preferences.html +1178 -10
- nautobot/project-static/docs/docker/index.html +1178 -10
- nautobot/project-static/docs/index.html +1183 -12
- nautobot/project-static/docs/installation/centos.html +1178 -10
- nautobot/project-static/docs/installation/external-authentication.html +1178 -10
- nautobot/project-static/docs/installation/http-server.html +1178 -10
- nautobot/project-static/docs/installation/index.html +1178 -10
- nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
- nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
- nautobot/project-static/docs/installation/nautobot.html +1179 -11
- nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
- nautobot/project-static/docs/installation/services.html +1178 -10
- 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 +1178 -10
- nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
- nautobot/project-static/docs/installation/upgrading.html +1178 -10
- nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
- nautobot/project-static/docs/models/circuits/provider.html +1293 -103
- nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
- nautobot/project-static/docs/models/dcim/cable.html +1324 -103
- nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/device.html +1326 -132
- nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
- nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
- nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/interface.html +1293 -103
- nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
- nautobot/project-static/docs/models/dcim/location.html +1293 -103
- nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
- nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
- nautobot/project-static/docs/models/dcim/platform.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
- nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
- nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
- nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/rack.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
- nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
- nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
- nautobot/project-static/docs/models/dcim/region.html +1178 -10
- nautobot/project-static/docs/models/dcim/site.html +1178 -10
- nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
- nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
- nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
- nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
- nautobot/project-static/docs/models/extras/customfield.html +1180 -12
- nautobot/project-static/docs/models/extras/customlink.html +1180 -12
- nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
- nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
- nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
- nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
- nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
- nautobot/project-static/docs/models/extras/job.html +1277 -86
- nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
- nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
- nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
- nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
- nautobot/project-static/docs/models/extras/note.html +1180 -12
- nautobot/project-static/docs/models/extras/relationship.html +1182 -14
- nautobot/project-static/docs/models/extras/role.html +1320 -86
- nautobot/project-static/docs/models/extras/secret.html +1314 -86
- nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
- nautobot/project-static/docs/models/extras/status.html +1188 -59
- nautobot/project-static/docs/models/extras/tag.html +1180 -12
- nautobot/project-static/docs/models/extras/webhook.html +1180 -12
- nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
- nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
- nautobot/project-static/docs/models/ipam/rir.html +1276 -86
- nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
- nautobot/project-static/docs/models/ipam/service.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
- nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
- nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
- nautobot/project-static/docs/models/users/token.html +1276 -86
- nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
- nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
- nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/plugins/development.html +1726 -495
- nautobot/project-static/docs/plugins/index.html +1178 -10
- nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
- nautobot/project-static/docs/release-notes/index.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
- nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
- nautobot/project-static/docs/requirements.txt +1 -0
- nautobot/project-static/docs/rest-api/authentication.html +1179 -11
- nautobot/project-static/docs/rest-api/filtering.html +1178 -10
- nautobot/project-static/docs/rest-api/overview.html +1841 -446
- 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 +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
- nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
- nautobot/project-static/docs/user-guides/graphql.html +1178 -10
- nautobot/project-static/docs/user-guides/relationships.html +1178 -10
- nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
- nautobot/project-static/js/forms.js +16 -9
- nautobot/project-static/js/theme.js +5 -0
- nautobot/tenancy/api/serializers.py +4 -32
- nautobot/tenancy/api/urls.py +1 -1
- nautobot/tenancy/forms.py +0 -28
- nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
- nautobot/tenancy/models.py +0 -25
- nautobot/tenancy/navigation.py +6 -39
- nautobot/tenancy/templates/tenancy/tenant.html +12 -12
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/tests/test_api.py +1 -3
- nautobot/tenancy/tests/test_filters.py +10 -5
- nautobot/tenancy/views.py +0 -2
- 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/tests/test_api.py +110 -25
- nautobot/virtualization/api/serializers.py +18 -130
- nautobot/virtualization/api/urls.py +1 -1
- nautobot/virtualization/api/views.py +1 -22
- nautobot/virtualization/forms.py +13 -99
- nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
- nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
- nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
- nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
- 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/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
- nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
- nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
- nautobot/virtualization/models.py +31 -123
- nautobot/virtualization/navigation.py +18 -99
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
- nautobot/virtualization/tests/test_api.py +25 -26
- nautobot/virtualization/tests/test_filters.py +41 -15
- nautobot/virtualization/tests/test_models.py +31 -7
- nautobot/virtualization/tests/test_views.py +42 -25
- nautobot/virtualization/views.py +7 -6
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
- {nautobot-2.0.0a3.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 -41
- nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
- 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 -18
- 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 -159
- nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
- nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
- nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
- nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
- 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.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# UI Related API Endpoints
|
|
2
|
+
|
|
3
|
+
+++ 2.0.0
|
|
4
|
+
|
|
5
|
+
Nautobot now retrieves its UI component information from API endpoints.
|
|
6
|
+
|
|
7
|
+
!!!important
|
|
8
|
+
These UI-related API endpoints serve a very different purpose from the rest. While we encourage users to utilize other API endpoints whenever possilbe in custom development, these API endpoints are not for external users and for internal use only. Therefore, they are not subject to Semantic Versioning and can change at any time to fit Nautobot UI needs until we decide that we are ready to support UI specific endpoints.
|
|
9
|
+
Note that these API endpoints are hidden from OpenAPI schema as well.
|
nautobot/extras/admin.py
CHANGED
|
@@ -36,14 +36,13 @@ class FileProxyAdmin(NautobotModelAdmin):
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
#
|
|
39
|
-
# Job results (jobs
|
|
39
|
+
# Job results (jobs and Git repository sync)
|
|
40
40
|
#
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@admin.register(JobResult)
|
|
44
44
|
class JobResultAdmin(NautobotModelAdmin):
|
|
45
45
|
list_display = [
|
|
46
|
-
"obj_type",
|
|
47
46
|
"name",
|
|
48
47
|
"date_created",
|
|
49
48
|
"date_done",
|
|
@@ -51,14 +50,13 @@ class JobResultAdmin(NautobotModelAdmin):
|
|
|
51
50
|
"status",
|
|
52
51
|
]
|
|
53
52
|
fields = [
|
|
54
|
-
"
|
|
53
|
+
"id",
|
|
55
54
|
"name",
|
|
56
55
|
"date_created",
|
|
57
56
|
"date_done",
|
|
58
57
|
"user",
|
|
59
58
|
"status",
|
|
60
|
-
"
|
|
61
|
-
"task_id",
|
|
59
|
+
"result",
|
|
62
60
|
]
|
|
63
61
|
list_filter = [
|
|
64
62
|
"status",
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
|
|
2
3
|
from drf_spectacular.types import OpenApiTypes
|
|
3
4
|
from drf_spectacular.utils import extend_schema_field
|
|
4
|
-
from rest_framework.
|
|
5
|
-
from rest_framework.fields import CreateOnlyDefault, Field
|
|
5
|
+
from rest_framework.fields import Field
|
|
6
6
|
|
|
7
|
-
from nautobot.core.api import ValidatedModelSerializer
|
|
8
|
-
from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
|
|
9
7
|
from nautobot.extras.models import CustomField
|
|
10
8
|
|
|
11
9
|
|
|
@@ -42,52 +40,30 @@ class CustomFieldDefaultValues:
|
|
|
42
40
|
|
|
43
41
|
@extend_schema_field(OpenApiTypes.OBJECT)
|
|
44
42
|
class CustomFieldsDataField(Field):
|
|
45
|
-
|
|
43
|
+
@property
|
|
44
|
+
def custom_field_keys(self):
|
|
46
45
|
"""
|
|
47
|
-
Cache
|
|
46
|
+
Cache CustomField keys assigned to this model to avoid redundant database queries
|
|
48
47
|
"""
|
|
49
|
-
if not hasattr(self, "
|
|
48
|
+
if not hasattr(self, "_custom_field_keys"):
|
|
50
49
|
content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
|
|
51
|
-
self.
|
|
52
|
-
|
|
50
|
+
self._custom_field_keys = CustomField.objects.filter(content_types=content_type).values_list(
|
|
51
|
+
"key", flat=True
|
|
52
|
+
)
|
|
53
|
+
return self._custom_field_keys
|
|
53
54
|
|
|
54
55
|
def to_representation(self, obj):
|
|
55
|
-
return {
|
|
56
|
+
return {key: obj.get(key) for key in self.custom_field_keys}
|
|
56
57
|
|
|
57
58
|
def to_internal_value(self, data):
|
|
58
59
|
"""Support updates to individual fields on an existing instance without needing to provide the entire dict."""
|
|
59
60
|
|
|
61
|
+
# Discard any entries in data that do not align with actual CustomFields - this matches the REST API behavior
|
|
62
|
+
# for top-level serializer fields that do not exist or are not writable
|
|
63
|
+
data = {key: value for key, value in data.items() if key in self.custom_field_keys}
|
|
64
|
+
|
|
60
65
|
# If updating an existing instance, start with existing _custom_field_data
|
|
61
66
|
if self.parent.instance:
|
|
62
67
|
data = {**self.parent.instance._custom_field_data, **data}
|
|
63
68
|
|
|
64
69
|
return data
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class CustomFieldModelSerializerMixin(ValidatedModelSerializer):
|
|
68
|
-
"""
|
|
69
|
-
Extends ModelSerializer to render any CustomFields and their values associated with an object.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
computed_fields = SerializerMethodField(read_only=True)
|
|
73
|
-
custom_fields = CustomFieldsDataField(
|
|
74
|
-
source="_custom_field_data",
|
|
75
|
-
default=CreateOnlyDefault(CustomFieldDefaultValues()),
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
@extend_schema_field(OpenApiTypes.OBJECT)
|
|
79
|
-
def get_computed_fields(self, obj):
|
|
80
|
-
return obj.get_computed_fields()
|
|
81
|
-
|
|
82
|
-
def get_field_names(self, declared_fields, info):
|
|
83
|
-
"""Ensure that "custom_fields" and "computed_fields" are always included appropriately."""
|
|
84
|
-
fields = list(super().get_field_names(declared_fields, info))
|
|
85
|
-
self.extend_field_names(fields, "custom_fields")
|
|
86
|
-
self.extend_field_names(fields, "computed_fields", opt_in_only=True)
|
|
87
|
-
return fields
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# TODO: remove in 2.2
|
|
91
|
-
@class_deprecated_in_favor_of(CustomFieldModelSerializerMixin)
|
|
92
|
-
class CustomFieldModelSerializer(CustomFieldModelSerializerMixin):
|
|
93
|
-
pass
|
nautobot/extras/api/fields.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
from django.forms.fields import CallableChoiceIterator
|
|
2
2
|
from rest_framework import serializers
|
|
3
3
|
|
|
4
|
-
from nautobot.core.api.mixins import LimitQuerysetChoicesSerializerMixin
|
|
5
|
-
from nautobot.extras.api.nested_serializers import NestedRoleSerializer, NestedStatusSerializer
|
|
6
|
-
|
|
7
4
|
|
|
8
5
|
class MultipleChoiceJSONField(serializers.MultipleChoiceField):
|
|
9
6
|
"""A MultipleChoiceField that renders the received value as a JSON-compatible list rather than a set."""
|
|
@@ -18,11 +15,3 @@ class MultipleChoiceJSONField(serializers.MultipleChoiceField):
|
|
|
18
15
|
def to_internal_value(self, data):
|
|
19
16
|
set_value = super().to_internal_value(data)
|
|
20
17
|
return sorted(set_value)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class RoleSerializerField(LimitQuerysetChoicesSerializerMixin, NestedRoleSerializer):
|
|
24
|
-
"""NestedSerializer field for `Role` object fields."""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class StatusSerializerField(LimitQuerysetChoicesSerializerMixin, NestedStatusSerializer):
|
|
28
|
-
"""NestedSerializer field for `Status` object fields."""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from nautobot.core.api import (
|
|
5
|
+
BaseModelSerializer,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaggedModelSerializerMixin(BaseModelSerializer):
|
|
12
|
+
def get_field_names(self, declared_fields, info):
|
|
13
|
+
"""Ensure that 'tags' field is always present except on nested serializers."""
|
|
14
|
+
fields = list(super().get_field_names(declared_fields, info))
|
|
15
|
+
if not self.is_nested:
|
|
16
|
+
self.extend_field_names(fields, "tags")
|
|
17
|
+
return fields
|
|
18
|
+
|
|
19
|
+
def create(self, validated_data):
|
|
20
|
+
tags = validated_data.pop("tags", None)
|
|
21
|
+
instance = super().create(validated_data)
|
|
22
|
+
|
|
23
|
+
if tags is not None:
|
|
24
|
+
return self._save_tags(instance, tags)
|
|
25
|
+
return instance
|
|
26
|
+
|
|
27
|
+
def update(self, instance, validated_data):
|
|
28
|
+
tags = validated_data.pop("tags", None)
|
|
29
|
+
|
|
30
|
+
# Cache tags on instance for change logging
|
|
31
|
+
instance._tags = tags or []
|
|
32
|
+
|
|
33
|
+
instance = super().update(instance, validated_data)
|
|
34
|
+
|
|
35
|
+
if tags is not None:
|
|
36
|
+
return self._save_tags(instance, tags)
|
|
37
|
+
return instance
|
|
38
|
+
|
|
39
|
+
def _save_tags(self, instance, tags):
|
|
40
|
+
if tags:
|
|
41
|
+
instance.tags.set([t.name for t in tags])
|
|
42
|
+
else:
|
|
43
|
+
instance.tags.clear()
|
|
44
|
+
|
|
45
|
+
return instance
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
|
-
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
5
4
|
from django.db.models import Q
|
|
6
5
|
from drf_spectacular.utils import extend_schema_field
|
|
7
|
-
from rest_framework.exceptions import PermissionDenied
|
|
8
6
|
from rest_framework.fields import JSONField
|
|
9
7
|
from rest_framework.reverse import reverse
|
|
10
8
|
from rest_framework.serializers import ValidationError
|
|
11
9
|
|
|
12
|
-
from nautobot.core.api import ValidatedModelSerializer
|
|
13
10
|
from nautobot.core.api.exceptions import SerializerNotFound
|
|
14
|
-
from nautobot.core.api.
|
|
11
|
+
from nautobot.core.api.mixins import WritableSerializerMixin
|
|
12
|
+
from nautobot.core.api.utils import (
|
|
13
|
+
get_relation_info_for_nested_serializers,
|
|
14
|
+
get_serializer_for_model,
|
|
15
|
+
nested_serializer_factory,
|
|
16
|
+
)
|
|
15
17
|
from nautobot.extras.choices import RelationshipSideChoices
|
|
16
|
-
from nautobot.extras.models import Relationship
|
|
17
|
-
|
|
18
|
+
from nautobot.extras.models import Relationship
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
@@ -39,15 +40,15 @@ side_data_schema = {
|
|
|
39
40
|
|
|
40
41
|
@extend_schema_field(
|
|
41
42
|
{
|
|
42
|
-
# Dictionary, keyed by relationship
|
|
43
|
+
# Dictionary, keyed by relationship key
|
|
43
44
|
"type": "object",
|
|
44
45
|
"additionalProperties": {
|
|
45
46
|
"type": "object",
|
|
46
|
-
"required": ["id", "url", "
|
|
47
|
+
"required": ["id", "url", "label", "type"],
|
|
47
48
|
"properties": {
|
|
48
49
|
"id": {"type": "string", "format": "uuid", "readOnly": True},
|
|
49
50
|
"url": {"type": "string", "format": "uri", "readOnly": True},
|
|
50
|
-
"
|
|
51
|
+
"label": {"type": "string", "readOnly": True},
|
|
51
52
|
"type": {"type": "string", "readOnly": True, "example": "one-to-many"},
|
|
52
53
|
"source": {"type": "object", "properties": side_data_schema},
|
|
53
54
|
"destination": {"type": "object", "properties": side_data_schema},
|
|
@@ -56,7 +57,7 @@ side_data_schema = {
|
|
|
56
57
|
},
|
|
57
58
|
}
|
|
58
59
|
)
|
|
59
|
-
class RelationshipsDataField(JSONField):
|
|
60
|
+
class RelationshipsDataField(WritableSerializerMixin, JSONField):
|
|
60
61
|
"""
|
|
61
62
|
Represent the set of all Relationships defined for a given model,
|
|
62
63
|
and all RelationshipAssociations per Relationship that apply to a specific instance of that model.
|
|
@@ -66,16 +67,18 @@ class RelationshipsDataField(JSONField):
|
|
|
66
67
|
to provide the entire dict of all relationships and associations.
|
|
67
68
|
"""
|
|
68
69
|
|
|
70
|
+
queryset = None
|
|
71
|
+
|
|
69
72
|
def to_representation(self, value):
|
|
70
73
|
"""
|
|
71
74
|
Get a JSON representation of all relationships and associations applicable to this model.
|
|
72
75
|
|
|
73
76
|
Returns:
|
|
74
77
|
{
|
|
75
|
-
"<
|
|
78
|
+
"<relationship_key>": {
|
|
76
79
|
"id": ...,
|
|
77
80
|
"url": ...,
|
|
78
|
-
"
|
|
81
|
+
"label": ...,
|
|
79
82
|
"type": "one-to-one|one-to-many|many-to-many|...",
|
|
80
83
|
# if this model can be the destination of the relationship:
|
|
81
84
|
"source": {
|
|
@@ -96,7 +99,7 @@ class RelationshipsDataField(JSONField):
|
|
|
96
99
|
"objects": [{...}, {...}, ...],
|
|
97
100
|
},
|
|
98
101
|
},
|
|
99
|
-
"<
|
|
102
|
+
"<relationship_key>": {
|
|
100
103
|
...
|
|
101
104
|
},
|
|
102
105
|
...
|
|
@@ -106,8 +109,9 @@ class RelationshipsDataField(JSONField):
|
|
|
106
109
|
relationships_data = value.get_relationships(include_hidden=True)
|
|
107
110
|
for this_side, relationships in relationships_data.items():
|
|
108
111
|
for relationship, associations in relationships.items():
|
|
112
|
+
depth = int(self.context.get("depth", 0))
|
|
109
113
|
data.setdefault(
|
|
110
|
-
relationship.
|
|
114
|
+
relationship.key,
|
|
111
115
|
{
|
|
112
116
|
"id": str(relationship.id),
|
|
113
117
|
"url": reverse(
|
|
@@ -115,42 +119,56 @@ class RelationshipsDataField(JSONField):
|
|
|
115
119
|
kwargs={"pk": relationship.id},
|
|
116
120
|
request=self.parent.context.get("request"),
|
|
117
121
|
),
|
|
118
|
-
"
|
|
122
|
+
"label": relationship.label,
|
|
119
123
|
"type": relationship.type,
|
|
120
124
|
},
|
|
121
125
|
)
|
|
122
126
|
other_side = RelationshipSideChoices.OPPOSITE[this_side]
|
|
123
|
-
data[relationship.
|
|
127
|
+
data[relationship.key][other_side] = {"label": relationship.get_label(this_side)}
|
|
124
128
|
|
|
125
129
|
other_type = getattr(relationship, f"{other_side}_type")
|
|
126
|
-
data[relationship.
|
|
130
|
+
data[relationship.key][other_side]["object_type"] = f"{other_type.app_label}.{other_type.model}"
|
|
127
131
|
|
|
128
132
|
# Get the nested serializer, if any, for the objects of other_type.
|
|
129
133
|
# This may fail, such as in the case of a plugin that has models but doesn't implement serializers,
|
|
130
134
|
# or in the case of a relationship involving models from a plugin that's not currently enabled.
|
|
131
135
|
other_side_serializer = None
|
|
132
136
|
other_side_model = other_type.model_class()
|
|
137
|
+
|
|
138
|
+
other_objects = [assoc.get_peer(value) for assoc in associations if assoc.get_peer(value) is not None]
|
|
133
139
|
if other_side_model is not None:
|
|
134
140
|
try:
|
|
135
|
-
|
|
141
|
+
depth = int(self.context.get("depth", 0))
|
|
142
|
+
if depth != 0:
|
|
143
|
+
if associations and other_objects:
|
|
144
|
+
relation_info = get_relation_info_for_nested_serializers(
|
|
145
|
+
associations[0], other_objects[0], f"{other_side}"
|
|
146
|
+
)
|
|
147
|
+
other_side_serializer, field_kwargs = self.build_nested_field(
|
|
148
|
+
f"{other_side}", relation_info, depth
|
|
149
|
+
)
|
|
136
150
|
except SerializerNotFound:
|
|
137
151
|
pass
|
|
138
152
|
|
|
139
|
-
other_objects = [assoc.get_peer(value) for assoc in associations if assoc.get_peer(value) is not None]
|
|
140
|
-
|
|
141
153
|
if other_side_serializer is not None:
|
|
142
|
-
data[relationship.
|
|
143
|
-
other_side_serializer(
|
|
154
|
+
data[relationship.key][other_side]["objects"] = [
|
|
155
|
+
other_side_serializer(
|
|
156
|
+
other_obj, context={"request": self.context.get("request")}, **field_kwargs
|
|
157
|
+
).data
|
|
158
|
+
for other_obj in other_objects
|
|
144
159
|
]
|
|
145
160
|
else:
|
|
146
161
|
# Simulate a serializer that contains nothing but the id field.
|
|
147
|
-
data[relationship.
|
|
162
|
+
data[relationship.key][other_side]["objects"] = [
|
|
148
163
|
{"id": other_obj.id} for other_obj in other_objects
|
|
149
164
|
]
|
|
150
165
|
|
|
151
166
|
logger.debug("to_representation(%s) -> %s", value, data)
|
|
152
167
|
return data
|
|
153
168
|
|
|
169
|
+
def build_nested_field(self, field_name, relation_info, nested_depth):
|
|
170
|
+
return nested_serializer_factory(relation_info, nested_depth)
|
|
171
|
+
|
|
154
172
|
def to_internal_value(self, data):
|
|
155
173
|
"""
|
|
156
174
|
Allow for updates to some relationships without overriding others.
|
|
@@ -186,20 +204,20 @@ class RelationshipsDataField(JSONField):
|
|
|
186
204
|
output_data[relationship]["peer"] = []
|
|
187
205
|
|
|
188
206
|
# Input validation - prevent referencing a relationship that doesn't exist or apply
|
|
189
|
-
|
|
190
|
-
for
|
|
191
|
-
if
|
|
207
|
+
relationship_keys = [relationship.key for relationship in relationships]
|
|
208
|
+
for relationship_key in data:
|
|
209
|
+
if relationship_key not in relationship_keys:
|
|
192
210
|
raise ValidationError(
|
|
193
|
-
f'"{
|
|
211
|
+
f'"{relationship_key}" is not a relationship on {self.parent.Meta.model._meta.label}'
|
|
194
212
|
)
|
|
195
213
|
|
|
196
214
|
for relationship in relationships:
|
|
197
|
-
if relationship.
|
|
215
|
+
if relationship.key not in data:
|
|
198
216
|
# No changes to any associations for this relationship, can disregard it
|
|
199
217
|
del output_data[relationship]
|
|
200
218
|
continue
|
|
201
219
|
|
|
202
|
-
relationship_data = data[relationship.
|
|
220
|
+
relationship_data = data[relationship.key]
|
|
203
221
|
|
|
204
222
|
for other_side in ["source", "destination", "peer"]:
|
|
205
223
|
if other_side not in relationship_data:
|
|
@@ -218,7 +236,7 @@ class RelationshipsDataField(JSONField):
|
|
|
218
236
|
# Don't allow omitting 'objects' altogether as a shorthand for deleting all associations
|
|
219
237
|
if "objects" not in relationship_data[other_side]:
|
|
220
238
|
raise ValidationError(
|
|
221
|
-
f'"objects" must be specified under ["{relationship.
|
|
239
|
+
f'"objects" must be specified under ["{relationship.key}"]["{other_side}"] when present'
|
|
222
240
|
)
|
|
223
241
|
objects_data = relationship_data[other_side]["objects"]
|
|
224
242
|
if not isinstance(objects_data, (list, tuple)):
|
|
@@ -237,144 +255,31 @@ class RelationshipsDataField(JSONField):
|
|
|
237
255
|
# Object lookup time!
|
|
238
256
|
other_type = getattr(relationship, f"{other_side}_type")
|
|
239
257
|
other_side_model = other_type.model_class()
|
|
258
|
+
self.queryset = other_side_model.objects
|
|
240
259
|
other_side_serializer = None
|
|
241
260
|
if other_side_model is None:
|
|
242
261
|
raise ValidationError(f"Model {other_type} is not currently installed, cannot look it up")
|
|
243
262
|
try:
|
|
244
|
-
other_side_serializer = get_serializer_for_model(other_side_model
|
|
263
|
+
other_side_serializer = get_serializer_for_model(other_side_model)
|
|
245
264
|
except SerializerNotFound as exc:
|
|
246
265
|
raise ValidationError(
|
|
247
266
|
f"No Nested{other_side_model}Serializer found, cannot deserialize it"
|
|
248
267
|
) from exc
|
|
249
268
|
|
|
250
|
-
|
|
251
|
-
serializer_instance = other_side_serializer(data=object_data, context=self.context)
|
|
252
|
-
# may raise ValidationError, let it bubble up if so
|
|
253
|
-
serializer_instance.is_valid()
|
|
269
|
+
depth = int(self.context.get("depth", 0))
|
|
254
270
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
271
|
+
if depth != 0:
|
|
272
|
+
for object_data in objects_data:
|
|
273
|
+
serializer_instance = other_side_serializer(data=object_data, context=self.context)
|
|
274
|
+
# may raise ValidationError, let it bubble up if so
|
|
275
|
+
serializer_instance.is_valid(raise_exception=True)
|
|
258
276
|
|
|
277
|
+
# We don't check/enforce relationship source_filter/destination_filter here, as that'll be handled
|
|
278
|
+
# later by `RelationshipAssociation.validated_save()` in RelationshipModelSerializerMixin.
|
|
279
|
+
output_data[relationship][other_side].append(serializer_instance.data)
|
|
280
|
+
else:
|
|
281
|
+
for object_data in objects_data:
|
|
282
|
+
instance = super().to_internal_value(object_data)
|
|
283
|
+
output_data[relationship][other_side].append({"id": instance.id})
|
|
259
284
|
logger.debug("to_internal_value(%s) -> %s", data, output_data)
|
|
260
285
|
return {self.field_name: output_data}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
class RelationshipModelSerializerMixin(ValidatedModelSerializer):
|
|
264
|
-
"""Extend ValidatedModelSerializer with a `relationships` field."""
|
|
265
|
-
|
|
266
|
-
relationships = RelationshipsDataField(required=False, source="*")
|
|
267
|
-
|
|
268
|
-
def create(self, validated_data):
|
|
269
|
-
relationships_data = validated_data.pop("relationships", {})
|
|
270
|
-
required_relationships_errors = self.Meta().model.required_related_objects_errors(
|
|
271
|
-
output_for="api", initial_data=relationships_data
|
|
272
|
-
)
|
|
273
|
-
if required_relationships_errors:
|
|
274
|
-
raise ValidationError({"relationships": required_relationships_errors})
|
|
275
|
-
instance = super().create(validated_data)
|
|
276
|
-
if relationships_data:
|
|
277
|
-
try:
|
|
278
|
-
self._save_relationships(instance, relationships_data)
|
|
279
|
-
except DjangoValidationError as error:
|
|
280
|
-
raise ValidationError(str(error))
|
|
281
|
-
return instance
|
|
282
|
-
|
|
283
|
-
def update(self, instance, validated_data):
|
|
284
|
-
relationships_key_specified = "relationships" in self.context["request"].data
|
|
285
|
-
relationships_data = validated_data.pop("relationships", {})
|
|
286
|
-
required_relationships_errors = self.Meta().model.required_related_objects_errors(
|
|
287
|
-
output_for="api",
|
|
288
|
-
initial_data=relationships_data,
|
|
289
|
-
relationships_key_specified=relationships_key_specified,
|
|
290
|
-
instance=instance,
|
|
291
|
-
)
|
|
292
|
-
if required_relationships_errors:
|
|
293
|
-
raise ValidationError({"relationships": required_relationships_errors})
|
|
294
|
-
|
|
295
|
-
instance = super().update(instance, validated_data)
|
|
296
|
-
if relationships_data:
|
|
297
|
-
self._save_relationships(instance, relationships_data)
|
|
298
|
-
return instance
|
|
299
|
-
|
|
300
|
-
def _save_relationships(self, instance, relationships):
|
|
301
|
-
"""Create/update RelationshipAssociations corresponding to a model instance."""
|
|
302
|
-
# relationships has already passed RelationshipsDataField.to_internal_value(), so we can skip some try/excepts
|
|
303
|
-
logger.debug("_save_relationships: %s : %s", instance, relationships)
|
|
304
|
-
for relationship, relationship_data in relationships.items():
|
|
305
|
-
for other_side in ["source", "destination", "peer"]:
|
|
306
|
-
if other_side not in relationship_data:
|
|
307
|
-
continue
|
|
308
|
-
|
|
309
|
-
other_type = getattr(relationship, f"{other_side}_type")
|
|
310
|
-
other_side_model = other_type.model_class()
|
|
311
|
-
other_side_serializer = get_serializer_for_model(other_side_model, prefix="Nested")
|
|
312
|
-
serializer_instance = other_side_serializer(context={"request": self.context.get("request")})
|
|
313
|
-
|
|
314
|
-
expected_objects_data = relationship_data[other_side]
|
|
315
|
-
expected_objects = [
|
|
316
|
-
serializer_instance.to_internal_value(object_data) for object_data in expected_objects_data
|
|
317
|
-
]
|
|
318
|
-
|
|
319
|
-
this_side = RelationshipSideChoices.OPPOSITE[other_side]
|
|
320
|
-
|
|
321
|
-
if this_side != RelationshipSideChoices.SIDE_PEER:
|
|
322
|
-
existing_associations = relationship.relationship_associations.filter(
|
|
323
|
-
**{f"{this_side}_id": instance.pk}
|
|
324
|
-
)
|
|
325
|
-
existing_objects = [assoc.get_peer(instance) for assoc in existing_associations]
|
|
326
|
-
else:
|
|
327
|
-
existing_associations_1 = relationship.relationship_associations.filter(source_id=instance.pk)
|
|
328
|
-
existing_objects_1 = [assoc.get_peer(instance) for assoc in existing_associations_1]
|
|
329
|
-
existing_associations_2 = relationship.relationship_associations.filter(destination_id=instance.pk)
|
|
330
|
-
existing_objects_2 = [assoc.get_peer(instance) for assoc in existing_associations_2]
|
|
331
|
-
existing_associations = list(existing_associations_1) + list(existing_associations_2)
|
|
332
|
-
existing_objects = existing_objects_1 + existing_objects_2
|
|
333
|
-
|
|
334
|
-
add_objects = []
|
|
335
|
-
remove_assocs = []
|
|
336
|
-
|
|
337
|
-
for obj, assoc in zip(existing_objects, existing_associations):
|
|
338
|
-
if obj not in expected_objects:
|
|
339
|
-
remove_assocs.append(assoc)
|
|
340
|
-
for obj in expected_objects:
|
|
341
|
-
if obj not in existing_objects:
|
|
342
|
-
add_objects.append(obj)
|
|
343
|
-
|
|
344
|
-
for add_object in add_objects:
|
|
345
|
-
if "request" in self.context and not self.context["request"].user.has_perm(
|
|
346
|
-
"extras.add_relationshipassociation"
|
|
347
|
-
):
|
|
348
|
-
raise PermissionDenied("This user does not have permission to create RelationshipAssociations.")
|
|
349
|
-
if other_side != RelationshipSideChoices.SIDE_SOURCE:
|
|
350
|
-
assoc = RelationshipAssociation(
|
|
351
|
-
relationship=relationship,
|
|
352
|
-
source_type=relationship.source_type,
|
|
353
|
-
source_id=instance.id,
|
|
354
|
-
destination_type=relationship.destination_type,
|
|
355
|
-
destination_id=add_object.id,
|
|
356
|
-
)
|
|
357
|
-
else:
|
|
358
|
-
assoc = RelationshipAssociation(
|
|
359
|
-
relationship=relationship,
|
|
360
|
-
source_type=relationship.source_type,
|
|
361
|
-
source_id=add_object.id,
|
|
362
|
-
destination_type=relationship.destination_type,
|
|
363
|
-
destination_id=instance.id,
|
|
364
|
-
)
|
|
365
|
-
assoc.validated_save() # enforce relationship filter logic, etc.
|
|
366
|
-
logger.debug("Created %s", assoc)
|
|
367
|
-
|
|
368
|
-
for remove_assoc in remove_assocs:
|
|
369
|
-
if "request" in self.context and not self.context["request"].user.has_perm(
|
|
370
|
-
"extras.delete_relationshipassociation"
|
|
371
|
-
):
|
|
372
|
-
raise PermissionDenied("This user does not have permission to delete RelationshipAssociations.")
|
|
373
|
-
logger.debug("Deleting %s", remove_assoc)
|
|
374
|
-
remove_assoc.delete()
|
|
375
|
-
|
|
376
|
-
def get_field_names(self, declared_fields, info):
|
|
377
|
-
"""Ensure that "relationships" is always included as an opt-in field."""
|
|
378
|
-
fields = list(super().get_field_names(declared_fields, info))
|
|
379
|
-
self.extend_field_names(fields, "relationships", opt_in_only=True)
|
|
380
|
-
return fields
|