Django REST Framework (DRF): ModelSerializer не проверяет модели при сериализации - PullRequest
0 голосов
/ 07 февраля 2020

Я хочу спросить, как правильно использовать Django REST Framework (DRF) ModelSerializer для сериализации из модели.

У меня есть Django модель с двумя обязательными полями:

class Book(models.Model):
    title = models.CharField()
    desc = models.CharField()

У меня есть DRF ModelSerializer:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'desc']

Я могу десерализовать и проверить входящий запрос, используя:

serializer = BookSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

Но как сериализовать и отправить ответ? DRF позволяет мне разорвать контакт, построенный с использованием ModelSerializer. Если я забыл установить одно из обязательных полей Book, оно все равно будет проходить через BookSerializer!

invalid_book = Book(title="Foo")    # but forgotten to set "desc"
serializer = BookSerializer(instance=invalid_book)
serializer.data        # it contains book without required "desc"

Сериализованный, созданный с использованием параметра параметра instance, выдает ошибку, если я попытаюсь is_validate().

Почему ModelSerializer может проверить входящие данные, но не может проверить исходящие?

Ответы [ 2 ]

0 голосов
/ 10 февраля 2020

Вы ошибаетесь. Сериализатор (не десериализатор) делает одну вещь. Преобразовать объект в JSON. Здесь вы создаете объект Book (name = 'sad book'). Это обычный объект Python. Django Сериализаторы будут пытаться сериализовать любой Объект, который ему передан.

Вас может удивить, что это поле обязательно для заполнения в Model, но почему сериализатор не проверяет? Из-за способа DRF обрабатывает сериализацию. Я покажу некоторые выдержки из исходного кода DRF.

Так вычисляется свойство данных.

class BaseSerializer():
...
...
@property
def data(self):
    if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
        msg = (
            'When a serializer is passed a `data` keyword argument you '
            'must call `.is_valid()` before attempting to access the '
            'serialized `.data` representation.\n'
            'You should either call `.is_valid()` first, '
            'or access `.initial_data` instead.'
        )
        raise AssertionError(msg)


    if not hasattr(self, '_data'):
        if self.instance is not None and not getattr(self, '_errors', None):
            # THIS IS WHERE WE GO. THE to_representation() CAN BE FOUND IN THE IMPLEMENTATION
            # OF ModelSerializer() which inherits from this class BaseSerializer
            self._data = self.to_representation(self.instance)
        elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
            self._data = self.to_representation(self.validated_data)
        else:
            self._data = self.get_initial()
    return self._data

Что происходит в ModelSerializer.to_representation ()?

class ModelSerializer(BaseSerializer):
    ...
    ...

    def to_representation(self, instance):
    """
    Object instance -> Dict of primitive datatypes.
    """
    ret = OrderedDict()
    fields = self._readable_fields

    for field in fields:
        try:
            attribute = field.get_attribute(instance)
        except SkipField:
            continue

        # We skip `to_representation` for `None` values so that fields do
        # not have to explicitly deal with that case.
        #
        # For related fields with `use_pk_only_optimization` we need to
        # resolve the pk value.
        check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
        if check_for_none is None:
            ret[field.field_name] = None
        else:
            ret[field.field_name] = field.to_representation(attribute)

    return ret

Как видите, в этом случае сериализатор отображает только поля из переданного объекта. Таким образом, нет проверки во время сериализации. Для получения дополнительной информации, проверьте исходный код DRF. Это довольно легко, если вы используете Pycharm Pro.

0 голосов
/ 07 февраля 2020

Проверка выполняется только при десериализации. Согласно документации :

Сериализаторы также обеспечивают десериализацию, позволяя преобразовывать проанализированные данные обратно в сложные типы после первой проверки входящих данных.

Это имеет смысл ( edit : таким образом, Django Rest Framework, по-видимому, истолковывается). Поскольку роль Serializer не в том, чтобы убедиться, что ваши сложные данные, такие как наборы запросов и экземпляры модели (например, ваш Book экземпляр), которые вы собираетесь сериализовать, интерпретируются «законно», поэтому они также не не проверять при сериализации.

Поэтому, если вы сохраните экземпляр как invalid_book.save(), Django выдаст ошибку из-за пропущенного поля.

Редактировать

После комментария о том, чтобы быть «точкой зрения» и, следовательно, быть осуждаемым, я хочу подчеркнуть и пояснить, что, похоже, именно таким образом истолковывается Django Rest Framework (DRF). После более глубокого изучения SO я связываю этот ответ в службу поддержки.

Также, если вы читаете документацию по DRF, подразумевается, что сериализация и проверка - это две разные концепции.

Кроме того, анализ serializers.py проясняет, что проверка запускается только при вызове is_valid() и проверка выполняется только на предоставленной data флаг. На самом деле, он даже не может быть запущен, когда предоставляется только экземпляр:

def __init__(self, instance=None, data=empty, **kwargs):
    self.instance = instance
    if data is not empty:
        self.initial_data = data
    self.partial = kwargs.pop('partial', False)
    self._context = kwargs.pop('context', {})
    kwargs.pop('many', None)
    super().__init__(**kwargs)

...

def is_valid(self, raise_exception=False):
    assert hasattr(self, 'initial_data'), (
        'Cannot call `.is_valid()` as no `data=` keyword argument was '
        'passed when instantiating the serializer instance.'
    )

    if not hasattr(self, '_validated_data'):
        try:
            self._validated_data = self.run_validation(self.initial_data)
        except ValidationError as exc:
            self._validated_data = {}
            self._errors = exc.detail
        else:
            self._errors = {}

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

    return not bool(self._errors)
...