Плоский фреймворк Django REST, сериализатор для чтения и записи

Что нужно для создания плоского представления сериализатора для чтения и записи в среде Django REST? Документы относятся к «плоскому представлению» (конец раздела http://django-rest-framework.org/api-guide/serializers.html#dealing-with-nested-objects), но не предлагайте примеры или что-либо, кроме предложения использовать RelatedField подкласс.

Например, как обеспечить плоское представление отношения User и UserProfile ниже?

# Model
class UserProfile(models.Model):
    user = models.OneToOneField(User)
    favourite_number = models.IntegerField()

# Serializer
class UserProfileSerializer(serializers.ModelSerializer):
    email = serialisers.EmailField(source='user.email')
    class Meta:
        model = UserProfile
        fields = ['id', 'favourite_number', 'email',]

Вышеупомянутое UserProfileSerializer не позволяет писать в поле email, но я надеюсь, что оно достаточно хорошо выражает намерение. Итак, как должен быть построен «плоский» сериализатор для чтения и записи, чтобы разрешить запись атрибута email в UserProfileSerializer? Возможно ли это сделать при создании подкласса ModelSerializer?

Спасибо.


person Paul Pepper    schedule 19.08.2013    source источник
comment
Здесь я тоже нашел ценный ответ https://stackoverflow.com/questions/21381700/django-rest-framework-how-do-you-flatten-nested-data   -  person Gers    schedule 14.08.2020


Ответы (3)


Глядя на исходный код Django REST framework (DRF), я пришел к выводу, что сериализатор DRF сильно привязан к сопровождающей модели для целей десериализации. Параметр source Field делает это менее удобным для целей сериализации.

Имея это в виду и рассматривая сериализаторы как инкапсулирующие поведение проверки и сохранения (в дополнение к их (не)сериализующему поведению), я использовал два сериализатора: по одному для каждой из моделей User и UserProfile:

class UserSerializer(serializer.ModelSerializer):
    class Meta:
        model = User
        fields = ['email',]

class UserProfileSerializer(serializer.ModelSerializer):
    email = serializers.EmailField(source='user.email')
    class Meta:
        model = UserProfile
        fields = ['id', 'favourite_number', 'email',]

Параметр source в EmailField адекватно обрабатывает случай сериализации (например, при обслуживании запросов GET). Для десериализации (например, при обслуживании запросов PUT) необходимо немного поработать в представлении, объединив проверку и сохранение поведения двух сериализаторов:

class UserProfileRetrieveUpdate(generics.GenericAPIView):
    def get(self, request, *args, **kwargs):
        # Only UserProfileSerializer is required to serialize data since
        # email is populated by the 'source' param on EmailField.
        serializer = UserProfileSerializer(
                instance=request.user.get_profile())
        return Response(serializer.data)

    def put(self, request, *args, **kwargs):
        # Both UserProfileSerializer and UserProfileSerializer are required
        # in order to validate and save data on their associated models.
        user_profile_serializer = UserProfileSerializer(
                instance=request.user.get_profile(),
                data=request.DATA)
        user_serializer = UserSerializer(
                instance=request.user,
                data=request.DATA)
        if user_profile_serializer.is_valid() and user_serializer.is_valid():
            user_profile_serializer.save()
            user_serializer.save()
            return Response(
                    user_profile_serializer.data, status=status.HTTP_200_OK)
        # Combine errors from both serializers.
        errors = dict()
        errors.update(user_profile_serializer.errors)
        errors.update(user_serializer.errors)
        return Response(errors, status=status.HTTP_400_BAD_REQUEST)
person Paul Pepper    schedule 20.08.2013
comment
Пол, ваш request.DATA здесь представляет собой один вложенный массив JSON или у вас есть по одному для каждой из моделей в запросе POST? (Пытаюсь добиться чего-то подобного) - person jvc26; 24.10.2013
comment
@ jvc26, в моем примере выше будет использоваться один корневой объект JSON в request.DATA. request.DATA будет выглядеть примерно так: { 'id': '1', 'favourite_number': '2', 'email': '[email protected]' } Что касается клиента, этот объект JSON представляет собой один экземпляр модели и не будет знать две модели (User и UserProfile), на которые он фактически разбивается на сервере. UserSerializer и UserProfileSerializer используются для извлечения, проверки и сохранения контента из request.DATA для связанных с ними моделей. - person Paul Pepper; 25.10.2013

Во-первых: скоро будет улучшена обработка вложенных операций записи.

Во-вторых: Документы по отношениям сериализаторов говорят о PrimaryKeyRelatedField и SlugRelatedField, что "По по умолчанию это поле доступно для чтения и записи..." — поэтому, если ваше поле электронной почты было уникальным (так ли это?), возможно, вы могли бы использовать SlugRelatedField, и это просто сработало бы — я еще не пробовал (однако).

В-третьих: вместо этого я использовал простой подкласс Field, который использует source="*" техника для принятия всего объекта. Оттуда я вручную извлекаю связанное поле в to_native и возвращаю его — это только для чтения. Чтобы написать, я проверил request.DATA в post_save и обновил там связанный объект — это не происходит автоматически, но работает.

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

person Carlton Gibson    schedule 20.08.2013
comment
Спасибо, @carton-gibson. (1) Я буду с нетерпением ждать более легкой записи во вложенных представлениях. (2) Мой пример упрощен для иллюстрации, но ваше предложение интересно. (3 и 4) Мне пришло в голову выполнить некоторую возню с post_save(), и я использовал это в других ситуациях, но использование двух сериализаторов (мой собственный ответ) кажется более чистым и надежным. - person Paul Pepper; 20.08.2013

Хотя это не дает строгого ответа на вопрос - я думаю, это решит вашу потребность. Проблема может заключаться скорее в разделении двух моделей для представления одной сущности, чем в проблеме с DRF.

Начиная с Django 1.5, вы можете создать собственного пользователя, если все, что вам нужно, это какой-то метод и дополнительные поля, но помимо этого вы довольны пользователем Django, тогда все, что вам нужно сделать, это:

class MyUser(AbstractBaseUser): favourite_number = models.IntegerField()

и в настройках: AUTH_USER_MODEL = 'myapp.myuser'

(И, конечно же, db-миграция, которую можно сделать довольно просто, используя опцию db_table, чтобы указать на существующую пользовательскую таблицу и просто добавить туда новые столбцы).

После этого у вас есть общий случай, в котором превосходит DRF.

person j-a    schedule 22.04.2014