Соответствие с Django import_export с несколькими полями

Я хочу импортировать CSV в Django. Проблема возникает при попытке импорта на основе атрибутов. Вот мой код:

class Event(models.Model):
    id = models.BigAutoField(primary_key=True)
    amount = models.ForeignKey(Amount, on_delete=models.CASCADE)
    value = models.FloatField()
    space = models.ForeignKey(Space, on_delete=models.RESTRICT)
    time = models.ForeignKey(Time, on_delete=models.RESTRICT)

    class Meta:
        managed = True
        db_table = "event"

class Space(models.Model):
    objects = SpaceManager()

    id = models.BigAutoField(primary_key=True)
    code = models.CharField(max_length=100)
    type = models.ForeignKey(SpaceType, on_delete=models.RESTRICT)
    space_date = models.DateField(blank=True, null=True)

    def natural_key(self):
        return self.code  # + self.type + self.source_date

    def __str__(self):
        return f"{self.name}"

    class Meta:
        managed = True
        db_table = "space"

class Time(models.Model):
    objects = TimeManager()

    id = models.BigAutoField(primary_key=True)
    type = models.ForeignKey(TimeType, on_delete=models.RESTRICT)
    startdate = models.DateTimeField()
    enddate = models.DateTimeField()

    def natural_key(self):
        return self.name

    def __str__(self):
        return f"{self.name}"

    class Meta:
        managed = True
        db_table = "time"

Теперь я создаю ресурс, который должен найти нужные объекты, но, похоже, он вообще не входит в ForeignKeyWidget(s):

class AmountForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row=None, **kwargs):
        logger.critical("<<<<< {AmountForeignKeyWidget} <<<<<<<")
        name_upper = value.upper()
        amount = Amount.objects.get_by_natural_key(name=name_upper)
        return amount


class SpaceForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        logger.critical("<<<<< {SpaceForeignKeyWidget} <<<<<<<")
        space_code = row["space_code"]
        space_type = SpatialDimensionType.objects.get_by_natural_key(row["space_type"])
        try:
            space_date = datetime.strptime(row["space_date"], "%Y%m%d")
        except ValueError:
            space_date = None
        space = Space.objects.get(
            code=space_code, type=space_type, source_date=space_date
        )
        return space


class TimeForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        logger.critical("<<<<< {TimeForeignKeyWidget} <<<<<<<")
        time_type = TimeType.objects.get_by_natural_key(row["time_type"])
        time_date = parse_datetime(row["time_date"])
        time = Time.objects.get_or_create(
            type=time_type, startdate=time_date), defaults={...}
        )
        return time


class EventResource(ModelResource):
    amount = Field(
        column_name="amount",
        attribute="amount",
        widget=AmountForeignKeyWidget(Amount),
    )
    space = Field(
        # column_name="space_code",
        attribute="space",
        widget=SpaceForeignKeyWidget(Space),
    )
    time = Field(
        attribute="time",
        widget=TimeForeignKeyWidget(Time),
    )

    def before_import_row(self, row, row_number=None, **kwargs):
        logger.error(f">>>> before_import_row() >>>>>>")
        time_date = datetime.strptime(row["time_date"], "%Y%m%d").date()
        time_type = TimeType.objects.get_by_natural_key(row["time_type"])
        Time.objects.get_or_create(
            type=time_type, startdate=time_date,
            defaults={
                "name": str(time_type) + str(time_date),
                "type": time_type,
                "startdate": time_date,
                "enddate": time_date + timedelta(days=1),
            },
        )

    class Meta:
        model = Event

Я добавил несколько логгеров, но распечатываю лог только в AmountForeignKeyWidget. Главный вопрос: Как искать объекты в Space по атрибутам (space_code,space_type,space_date) и в Time искать и создавать по (time_date,time_type). Меньший вопрос - почему не используются SpaceForeignKeyWidget и TimeForeignKeyWidget?

Основной вопрос: Как искать объекты в Space по атрибутам (space_code,space_type,space_date) и в Time искать и создавать по (time_date,time_type)

.

Похоже, что вы правильно ищете эти объекты, но, возможно, вызов не происходит. Часто при использовании import-export вы сэкономите себе много времени, если настроите отладчик и пройдете по коду.

Возможно, в вашем исходном csv нет столбца 'пробел' или 'время'. Если таких полей нет, то процесс импорта будет молча пропускать это объявление. Если вам нужно создать объекты, если они не существуют, то, вероятно, лучше использовать before_import_row() для этого.

Меньший вопрос - почему не используются SpaceForeignKeyWidget и TimeForeignKeyWidget?

Как и выше, если в исходных данных нет столбцов 'space' или 'time', то они никогда не будут вызваны.

Не должно иметь значения, но ваша декларация метода clean() не определяет row как kwarg в SpaceForeignKeyWidget и TimeForeignKeyWidget. Измените определение clean() на:

def clean(self, value, row=None, **kwargs):
    #  your implementation here

Я не вижу, что это исправит проблему, но, возможно, при запуске в вашем контексте это является проблемой.

Обратите внимание, что есть некоторые изменения, которые вы можете сделать для улучшения вашего кода.

Для AmountForeignKeyWidget, если вам нужно искать только по одному значению, вы можете изменить объявление ресурса следующим образом:

class EventResource(ModelResource):
    amount = Field(
        column_name="amount",
        attribute="amount",
        widget=ForeignKeyWidget(Amount, field="name__iexact"),
    )

Вам не нужно никакой дополнительной логики, и поиск будет нечувствителен к регистру.

Мне удалось решить все проблемы и сделать правильный импорт. Ниже приведен код, который я использовал:

class EventResource(ModelResource):
    amount = Field(
        column_name="amount",
        attribute="amount",
        widget=ForeignKeyWidget(Amount, field="name__iexact"),
    )
    space_code = Field(
        attribute="space",
        widget=SpaceForeignKeyWidget(Space),
    )
    time_date = Field(
        attribute="time",
        widget=TimeForeignKeyWidget(Time),
    )

    class Meta:
        model = Event

Для поля amount мне не нужно делать производный виджет, так как он использует только одну переменную в CSV. Для двух других реализация последует. Я заметил, что виджеты для двух других переменных не вызывались, и причина в том, что имена переменных не существовали в моем CSV-файле. Когда я переименовал их в имена столбцов, существующих в CSV, они были вызваны.

class SpaceForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        space_code = row["spacial_code"]
        space_type = SpaceDimensionType.objects.get(type=row["space_type"])
        try:
            space_date = datetime.strptime(row["space_date"], "%Y%m%d")
        except ValueError:
            space_date = None

        space = SpaceDimension.objects.get(
            code=space_code, type=space_type, source_date=space_date
        )
        return space


class TimeForeignKeyWidget(ForeignKeyWidget):
    def clean(self, value, row, **kwargs):
        time_type = TimeDimensionType.objects.get(type=row["time_type"])
        delta = T_TYPES[time_type]

        start_date = datetime.strptime(row["time_date"], "%Y%m%d").date()
        end_date = start_date + timedelta(days=delta)
        time, created = TimeDimension.objects.get_or_create(
            type=time_type,
            startdate=start_date,
            enddate=start_date + timedelta(days=delta),
            defaults={
                "name": f"{time_type}: {start_date}-{end_date}",
                "type": time_type,
                "startdate": start_date,
                "enddate": end_date,
            },
        )
        return temporal

SpaceForeignKeyWidget только ищет существующую запись и возвращает объект, а TimeForeignKeyWidget создает несуществующую и возвращает запись. Таким образом, нет необходимости использовать before_import_row() и вся логика локализована на этих двух виджетах.

Вернуться на верх