Изменение экземпляра до его сохранения при импорте

Я настраиваю процесс импорта для модели, которая имеет отношение ForeignKey, и у меня есть дополнительное поле в модели, в котором я хотел бы хранить предоставленное значение, если это недопустимый ID связанной таблицы.

В данном случае речь идет об идентификаторах UUID, поэтому велика вероятность того, что кто-то из участников предоставит некорректные данные.

class Result(models.Model):

    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False
    )
    passport = models.ForeignKey(
        to='results.Passport',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )
    invalid_passport = models.CharField(
        max_length=255,
        blank=True,
        null=True,
        help_text=_("An invalid passport number")
    )

Файл импорта будет содержать колонку passport для UUID связанного объекта, но я хотел бы сохранить предоставленное значение в invalid_passport, если оно не соответствует ни одному Passport экземпляру.

Мой ресурс выглядит следующим образом;

class ResultResource(resources.ModelResource):
    """ Integrate django-import-export with the Result model """
    passport = fields.Field(
        column_name='passport',
        attribute='passport',
        widget=ForeignKeyWidget(Passport, 'id')
    )

    class Meta:
        instance_loader_class = CachedInstanceLoader
        model = Result
        fields = (
            'id',
            'passport',
            'invalid_passport'
        )

    def init_instance(self, row=None):
        """ Initializes a new Django model. """
        instance = self._meta.model()
        if not instance.passport:
            instance.invalid_passport = row['passport']
        return instance

    def before_save_instance(self, instance, using_transactions, dry_run):
        """ Sets the verified flags where there is a passport match. """
        if instance.passport:
            instance.verified = True
            instance.verified_by = self.import_job.author
            instance.invalid_passport = None
        else:
            pass

Поэтому при создании экземпляра значение идентификатора Passport сохраняется в invalid_passport. Моя теория заключается в том, что в before_save_instance я могу определить, существует ли связанный Passport, за исключением того, что обе стороны if выполняются в before_save_instance.

Является ли это неправильным подходом для этого?

ok, я думаю, что у меня есть решение, возможно, оно не идеально, но я расскажу вам, что является подходящим инструментом, который очень помог мне в таких задачах. Итак, я думаю, что signals является лучшим способом для успешного выполнения этой задачи

Итак, я расскажу вам о шагах, которые я получил в псевдокоде, и вы можете изменить их в соответствии с вашей идеей:

  • вы будете использовать pre_save для получения данных, которые были размещены в запросе
  • в функции, к которой вы будете применять сигналы, откройте ваш CSV файл
  • .
  • возьмите UUID и найдите его в CSV-файле
  • проведите сравнение между данными, полученными из CSV-файла, и UUID
  • .
  • если данные не соответствуют UUID или любому другому полю, с которым вы хотите сравнить, сохраните этот UUID в invalid_passport
  • .
  • в противном случае, не сохраняйте его и верните ваш ответ как успешный вид

подробнее о pre_save и сигналах вы можете прочитать здесь: https://docs.djangoproject.com/en/4.0/ref/signals/

подсказка:
самым быстрым способом открыть файл и получить данные из файла будет использование dataframe в pandas, если вы знакомы с pandas, вы сможете решить эту задачу быстрее, чем использование открытого файла в Django, а также использовать высокую производительность в поиске, потому что в pandas вам не нужно будет использовать цикл для поиска большого количества данных, в отличие от обычного цикла в python

Одна проблема заключается в том, что если паспорт не существует, то ForeignKeyWidget вызовет исключение DoesNotExist и ваша строка не будет импортирована и будет помечена как ошибка. Однако, это может быть нормально для вас, потому что вас может интересовать только запись строк без ошибок.

Так что, если я чего-то не понимаю, я не могу понять, как вызывается before_save_instance(), потому что сначала вызывается исключение DoesNotExist, что означает, что ни одна из логик сохранения не вызывается.

Если вы посмотрите на эту строку, то увидите, что исключение добавляется в объект Error, а затем сохраняется в результатах для импорта. Это кажется хорошим вариантом для записи недействительных идентификаторов паспортов. На самом деле, существующий объект Error будет записывать исключение (DoesNotExist) и строку, так что у вас уже будет запись неудачных строк без какой-либо настройки. Вы можете переопределить объект Result или Error, чтобы иметь больше контроля над этим.

Другой идеей может быть переопределение before_import() для выполнения некоторой предварительной обработки. В before_import() вы можете загрузить все существующие uuids паспорта в память, а затем просканировать набор данных, чтобы удалить все несуществующие строки. Это может быть непрактично, если у вас большие таблицы.

Однако вы говорите, что хотите записать отсутствующий UUID в модель, поэтому вы можете ввести пользовательский подкласс ForeignKeyWidget, который не будет вызывать исключение DoesNotExist, а вместо этого может вызывать ValidationError. В этом случае ошибка будет храниться в import_validation_errors. Затем вы можете переопределить validate_instance(), чтобы прочитать ошибку обратно и сохранить ее в модели.

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