Skip to content

Commit

Permalink
Merge 7e26827 into cb4433e
Browse files Browse the repository at this point in the history
  • Loading branch information
yalef committed Jan 15, 2024
2 parents cb4433e + 7e26827 commit 1331136
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def get_readonly_fields(
readonly_fields.extend(
[
"resource_path",
"input_errors_file",
"data_file",
"resource_kwargs",
],
Expand Down Expand Up @@ -241,7 +242,10 @@ def get_fieldsets(
data = (
_("Importing data"),
{
"fields": ("_input_errors",),
"fields": (
"input_errors_file",
"_input_errors",
),
"classes": ("collapse",),
},
)
Expand Down
41 changes: 39 additions & 2 deletions import_export_extensions/api/serializers/import_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from celery import states

from ... import models, resources
from . import import_job_details as details
from .progress import ProgressSerializer


Expand All @@ -26,13 +27,49 @@ class ImportJobSerializer(serializers.ModelSerializer):

progress = ImportProgressSerializer()

import_params = details.ImportParamsSerializer(
read_only=True,
source="*",
)
totals = details.TotalsSerializer(
read_only=True,
source="*",
)
parse_error = serializers.CharField(
source="error_message",
read_only=True,
allow_blank=True,
)
input_error = details.InputErrorSerializer(
source="*",
read_only=True,
)
importing_data = details.ImportingDataSerializer(
read_only=True,
source="*",
)
input_errors_file = serializers.FileField(
read_only=True,
allow_null=True,
)
is_all_rows_shown = details.IsAllRowsShowField(
source="*",
read_only=True,
)

class Meta:
model = models.ImportJob
fields = (
"id",
"import_status",
"data_file",
"progress",
"import_status",
"import_params",
"totals",
"parse_error",
"input_error",
"is_all_rows_shown",
"importing_data",
"input_errors_file",
"import_started",
"import_finished",
"force_import",
Expand Down
155 changes: 155 additions & 0 deletions import_export_extensions/api/serializers/import_job_details.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import typing
from itertools import zip_longest

from rest_framework import serializers

from import_export.results import RowResult

from ... import models


class ImportParamsSerializer(serializers.Serializer):
"""Serializer for representing import parameters."""
data_file = serializers.FileField()
resource_path = serializers.CharField()
resource_kwargs = serializers.CharField()


class ImportDiffSerializer(serializers.Serializer):
"""Serializer for representing importing rows diff."""
previous = serializers.CharField(allow_blank=True, allow_null=True)
current = serializers.CharField(allow_blank=True, allow_null=True)


class ImportRowSerializer(serializers.Serializer):
"""Serializer for representing importing rows.
Used to generate correct openapi spec.
"""
operation = serializers.CharField()
parsed_fields = serializers.ListField(
child=ImportDiffSerializer(allow_null=True),
allow_null=True,
)


class ImportingDataSerializer(serializers.Serializer):
"""Serializer for representing importing data."""
headers = serializers.ListField(
child=serializers.CharField(),
)
rows = serializers.ListField(
child=ImportRowSerializer(),
)

def to_representation(self, instance: models.ImportJob):
"""Return dict with import details."""
if instance.import_status not in models.ImportJob.success_statuses:
return super().to_representation(self.get_initial())

rows = []
resource = instance.resource
for row in instance.result.rows:
# errors displayed in input_error.row_errors(InputErrorSerializer)
if row.import_type == RowResult.IMPORT_TYPE_ERROR:
continue

original_fields = [
resource.export_field(f, row.original) if row.original else ""
for f in resource.get_user_visible_fields()
]
current_fields = [
resource.export_field(f, row.instance)
for f in resource.get_user_visible_fields()
]

rows.append({
"operation": row.import_type,
"parsed_fields": [
{
"previous": v1,
"current": v2,
} for v1, v2 in zip_longest(original_fields, current_fields, fillvalue="")
],
})

importing_data = {
"headers": instance.result.diff_headers,
"rows": rows,
}
return super().to_representation(importing_data)


class TotalsSerializer(serializers.Serializer):
"""Serializer to represent import totals."""
new = serializers.IntegerField(allow_null=True, required=False)
update = serializers.IntegerField(allow_null=True, required=False)
delete = serializers.IntegerField(allow_null=True, required=False)
skip = serializers.IntegerField(allow_null=True, required=False)
error = serializers.IntegerField(allow_null=True, required=False)

def to_representation(self, instance):
"""Return dict with import totals."""
if instance.import_status not in models.ImportJob.results_statuses:
return super().to_representation(self.get_initial())
return super().to_representation(instance.result.totals)


class RowError(serializers.Serializer):
"""Represent single row errors."""
line = serializers.IntegerField()
error = serializers.CharField()
row = serializers.ListField(
child=serializers.CharField(),
)


class InputErrorSerializer(serializers.Serializer):
"""Represent Input errors."""
base_errors = serializers.ListField(
child=serializers.CharField(),
)
row_errors = serializers.ListField(
child=serializers.ListField(
child=RowError(),
),
)

def to_representation(self, instance: models.ImportJob):
"""Return dict with input errors."""
if instance.import_status not in models.ImportJob.results_statuses:
return super().to_representation(self.get_initial())

input_errors: dict[str, list[typing.Any]] = {
"base_errors": [],
"row_errors": [],
}

if instance.result.base_errors:
input_errors["base_errors"] = [
str(error.error) for error in instance.result.base_errors
]

if instance.result.row_errors():
for line, errors in instance.result.row_errors():
line_errors = [
{
"line": line,
"error": str(error.error),
"row": error.row.values(),
} for error in errors
]
input_errors["row_errors"].append(line_errors)

return super().to_representation(input_errors)


class IsAllRowsShowField(serializers.BooleanField):
"""Field for representing `all_rows_saved` value."""

def to_representation(self, instance):
"""Return boolean if all rows shown in importing data."""
if instance.import_status not in models.ImportJob.success_statuses:
return False
return instance.result.total_rows == len(instance.result.rows)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.7 on 2024-01-15 10:40

from django.db import migrations, models
import functools
import import_export_extensions.models.tools


class Migration(migrations.Migration):

dependencies = [
("import_export_extensions", "0005_importjob_force_import"),
]

operations = [
migrations.AddField(
model_name="importjob",
name="input_errors_file",
field=models.FileField(
help_text="File that contain failed rows",
max_length=512,
null=True,
upload_to=functools.partial(
import_export_extensions.models.tools.upload_file_to,
*(),
**{"main_folder_name": "import"}
),
verbose_name="Input errors file",
),
),
]
Loading

0 comments on commit 1331136

Please sign in to comment.