Как добавить проверку в сериализатор моделей, чтобы он гарантировал, что по крайней мере одно из двух полей передано точно?
Я создал модель на 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() таким образом даст вам желаемое поведение :)
Извините, если это был длинный ответ, надеюсь, что какая-то дополнительная ценность восполнит его!