Как добавить проверку в сериализатор моделей, чтобы он гарантировал, что по крайней мере одно из двух полей передано точно?

Я создал модель на Django, которая включает в себя два поля, назовем их file и code. файл - это поле файла, а код - поле символа. Я установил для них значения blank=True и null=Истина. Установите проверку в model, переопределив метод clean(), чтобы проверить, не существуют ли файл и код одновременно, он выдает ошибку проверки, в которой говорится, что необходимо передать хотя бы один из них. Вызывается функция full_clean() в переопределенном методе save().

Этот подход позволяет мне добавить такого рода по крайней мере одну проверку для этих двух полей на уровне модели и даже через панель администратора.

Но в сериализаторе или при обращении к API на самом деле не возникает никакой ошибки, если я не передаю оба параметра, вероятно, потому, что моя модель в основном говорит, что оба не требуются по отдельности.

Что я пытался сделать, так это переопределить метод validate() в сериализаторе, кстати, это modelserializer. В переопределенном методе validate() я добавил проверку следующим образом:

if file not in data and code not in data:
    raise serializers.ValidationError("At least one of the two must be passed.")

Что мне следует делать вместо этого? Даже изменение модели может помочь, но оно также должно сохранить эту проверку в панели администратора.

Я перечислил поля в Meta, но я также попытался явно создать поля для этих двух в сериализаторе и установить required=False, а затем обработать пользовательскую проверку в метод validate(), предложенный в некоторых других потоках.

Мы будем признательны за любую помощь! Спасибо!

  • Сохраняйте проверку на уровне модели (это нормально для администратора и целостности базы данных).

  • В вашем сериализаторе переопределите validate(), чтобы проверить, отсутствуют ли оба поля.

  • Сделайте поля сериализатора required=False такими, чтобы клиенты могли их опускать.

  • Вызовите full_clean() из своего save() и/или воспользуйтесь проверкой сериализатора отдельно.

    пример :

    Таким образом, проверки как администратора, так и API обеспечивают соблюдение правила

класс MyModelSerializer(сериализаторы.ModelSerializer): файл = сериализаторы.Поле файла(обязательно=False) код = сериализаторы.Поле ввода(обязательно=False)

def validate(self, data):
    file = data.get('file')
    code = data.get('code')
    if not file and not code:
        raise serializers.ValidationError("At least one of 'file' or 'code' must be provided.")
    return data

class Meta:
    model = MyModel
    fields = ['file', 'code', ...]

Я покопался в Интернете и выяснил, что именно так должна функционировать проверка DRF:
- сначала проверьте проверку на уровне полей: это означает, что программа проверяет каждое отдельное поле на наличие каких-либо проблем, таких как отсутствие обязательных полей. Если какое-либо поле не проходит проверку (например, не указано обязательное поле), оно сразу же останавливается и не переходит к проверке на уровне объекта, которая это цель метода validate(). => DRF даже не достигнет значения valdiate(), если будут какие-либо ошибки проверки на уровне поля .

validate() функция вызывается только в том случае, если все поля прошли проверку на уровне полей. Итак, параметр данных в методе validate() - это список полей, которые успешно прошли проверку, в вашем случае код и файл считаются успешно прошедшими проверку, поскольку они необязательны (required=False). Вот почему ошибка файла и кода отображается только тогда, когда передано все требуемое поле, как вы сказали здесь:

ПРАВКА: ошибки действительно возникают, но только после заполнения всех остальных обязательных полей в модели. Они не отображаются вместе со всем списком ошибок проверки.

Итак, я покопался еще глубже и нашел эту статью, в которой описывается эта проблема и способ решения, позволяющий добиться желаемого поведения. Решение, о котором он упомянул, - это переопределение метода is_valid(). я адаптировал его к вашему варианту использования, и вот как бы вы его реализовали:

class MySerializer(serializers.ModelSerializer):
    file = serializers.FileField(required=False)
    code = serializers.CharField(required=False)

    class Meta:
        model = MyModel
        fields = ("title", "file", "code")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.is_validated = False

    def is_valid(self, *, raise_exception=True):
        super().is_valid(raise_exception=False)

        if self._errors and not self.is_validated:
            try:
                self.validate(self.data)
            except serializers.ValidationError as e:
                for field, message in e.detail.items():
                    if field in self.fields:
                        if not isinstance(self._errors.get(field), list):
                            self._errors[field] = []
                        self._errors[field].append(message)

        if self._errors and raise_exception:
            raise serializers.ValidationError(self._errors)

        return not bool(self._errors)



    def validate(self, data):
        self.is_validated = True
        file = data.get('file')
        code = data.get('code')
        if not file and not code:
            raise serializers.ValidationError({
                'file': "Either file or code must be provided.",
                'code': "Either code or file must be provided.",
            })
        return data

Я внес некоторые изменения в появившуюся ошибку проверки в validate(), чтобы она стала ошибкой, относящейся к конкретному полю. и поскольку DRF выдает ошибку в виде списка, я также сделал так, чтобы ошибки кода и файла имели тип списка, поэтому был добавлен этот фрагмент кода:

for field, message in e.detail.items(): # <-- this loops over the raised list of errors (which are 2 from validate method)
                    if field in self.fields:
                        if not isinstance(self._errors.get(field), list): # <-- this checks if error is of type list, ik though in our case it's always string 
                            self._errors[field] = [] 
                        self._errors[field].append(message) # <-- this appends string error to the made list

Теперь возвращаемая ошибка выглядит так, как вы этого хотите!

{"title":["This field is required."],"file":["Either file or code must be provided."],"code":["Either code or file must be provided."]}

Примечание: Я бы посоветовал вам, однако, прочитать статью, в которой рассказывается о том, как реализован is_valid() и run_validations() и почему переопределение is_valid() таким образом даст вам желаемое поведение :)

Извините, если это был длинный ответ, надеюсь, что какая-то дополнительная ценность восполнит его!

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