it-swarm.com.ru

Django Rest Framework POST вложенные объекты

Я сейчас сталкиваюсь с небольшой проблемой с Django Rest Framework. Я пытаюсь опубликовать объект с вложенными объектами.

Вот мой serializers.py:

class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
        depth = 1

    def create(self, validated_data):
        return Exam.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.__dict__.update(**validated_data)
        instance.save()

        return instance

И create() из views.py:

def create(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)

    return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

А вот и ответ Почтальон:  Postman response

Я прочитал несколько сообщений здесь об этой проблеме, но я все еще застрял с ней. Я пытался исправить это несколькими способами, но он по-прежнему возвращает "This field is required.".

13
wencakisa

Вы имеете дело с проблемой вложенная сериализация. Пожалуйста, прочитайте связанную документацию, прежде чем продолжить. 

Ваш вопрос относится к сложной области проблем в DRF и, следовательно, требует некоторого объяснения и обсуждения для понимания того, как работают сериализаторы и наборы представлений.

Я буду обсуждать проблему представления ваших данных Subject и Class через одну и ту же конечную точку, используя различное представление данных для разных методов HTTP, потому что это обычно проблема, когда люди хотят представлять свои данные во вложенных форматах; они хотят предоставить своим пользовательским интерфейсам достаточно информации для чистого использования, например, через выпадающие селекторы.

По умолчанию Django и Django REST Framework (DRF) ссылаются на связанные объекты (ваши Subject и Class) по их первичным ключам. По умолчанию это целочисленные ключи с автоинкрементом Django. Если вы хотите обратиться к ним другими способами, вы должны написать переопределения для этого. Есть несколько разных вариантов.

  1. Первый вариант заключается в том, чтобы специализировать логику создания и обновления: Обратитесь к своему классу через какой-либо другой атрибут (ы) и вручную напишите поиск для создания, либо задайте ключ, через который вы ссылаетесь, как primary ключ вашего класса. Вы можете установить имя вашего класса, UUID или любой другой атрибут в качестве первичного ключа базы данных, при условии, что это уникальное, одиночное поле (причина, о которой я упоминаю, заключается в том, что вы в на данный момент, поиск вашей Class модели с составным поиском, который состоит из составного (числа, буквы) поискового термина). Например, вы можете переопределить поиск связанных объектов в вашем методе просмотра create (для POST), но тогда вам придется обрабатывать аналогичные поиски и в вашем методе просмотра update (для PUT и PATCH). 
  2. Во-вторых, на мой взгляд, предпочтительным вариантом является специализировать ваши представления объектов: Как правило, обращайтесь к своим классам через первичный ключ и создайте один сериализатор для чтения объект и один для создания и обновление это. Это может быть легко достигнуто путем наследования класса сериализатора и переопределения ваших представлений. Используйте первичный ключ в запросах POST, PUT, PATCH и т.д. Для обновления ссылок на классы и внешних ключей.

Вариант 1: поиск класса и субъекта с произвольным атрибутом при создании и обновлении:

Установите ваши вложенные сериализаторы классов только для чтения:

class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer(read_only=True)
    clazz = ClassSerializer(read_only=True)

Переопределите создание вашего представления для поиска связанных классов в атрибутах свободной формы. Также проверьте как DRF реализует это с помощью миксинов. Вам также придется переопределить свой метод update для правильной обработки и принять во внимание поддержку PATCH (частичное обновление) в дополнение к PUT (обновление), если вы выберете этот маршрут:

def create(self, request):
    # Look up objects by arbitrary attributes.
    # You can check here if your students are participating
    # the classes and have taken the subjects they sign up for.
    subject = get_object_or_404(Subject, title=request.data.get('subject'))
    clazz = get_object_or_404(
        Class, 
        number=request.data.get('clazz_number')
        letter=request.data.get('clazz_letter')
    )

    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(clazz=clazz, subject=subject)
    headers = self.get_success_headers(serializer.data)

    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

Вариант 2: Специализируйте ваши сериализаторы для чтения и записи и используйте первичные ключи; Это идиоматический подход:

Сначала определите ModelSerializer по умолчанию, который вы хотите использовать для обычных операций (POST, PUT, PATCH):

class ExamSerializer(serializers.ModelSerializer)
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

Затем замените необходимые поля типом представления, которое вы хотите дать им для чтения данных (GET):

class ExamReadSerializer(ExamSerializer):
     subject = SubjectSerializer(read_only=True)
     clazz = ClassSerializer(read_only=True)

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

class ExamViewSet(viewsets.ModelViewSet):
     queryset = Exam.objects.all()

     def get_serializer_class(self):
         # Define your HTTP method-to-serializer mapping freely.
         # This also works with CoreAPI and Swagger documentation,
         # which produces clean and readable API documentation,
         # so I have chosen to believe this is the way the
         # Django REST Framework author intended things to work:
         if self.request.method in ['GET']:
             # Since the ReadSerializer does nested lookups
             # in multiple tables, only use it when necessary
             return ExamReadSerializer
         return ExamSerializer

Как вы можете видеть, вариант 2 выглядит довольно менее сложным и подверженным ошибкам и содержит всего 3 строки рукописного кода поверх DRF (реализация get_serializer_class). Просто позвольте логике фреймворка выяснить представления и создания и обновления объектов для вас.

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

33
Aleksi Häkli

Более простой подход без дополнительных классов - взять сериализацию на себя:

class ExamSerializer(serializers.ModelSerializer):
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['subject'] = SubjectSerializer(
            Subject.objects.get(pk=data['subject'])).data
        data['clazz'] = ClassSerializer(
            Class.objects.get(pk=data['clazz'])).data
        return data
2
validname

У меня была такая же проблема при попытке разместить вложенный объект JSON в DRF (Django Rest Framework).

После того, как вы правильно настроили запись вложенных сериализаторов (см. Документы на записываемые вложенные сериализаторы ), вы можете проверить, работает ли он, используя browsable API и разместив/разместив там данные. Если это работает, и вы по-прежнему получаете ошибки «Это поле обязательно» на ваших вложенных моделях при публикации/размещении объектов JSON, возможно, вам придется установить тип содержимого вашего запроса.

Этот ответ предоставил решение, в котором я нуждался, и оно кратко изложено ниже.

$.ajax ({
  // Other parameters e.g. url, type
  data: JSON.stringify(data),
  dataType: "json",
  contentType: "application/json; charset=utf-8",
});

Мне нужно было установить «contentType», а также «stringify» моего объекта js.

0
Keoni Mahelona