Подтвердить что ты не робот

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." ,

4b9b3361

Ответ 1

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

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

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

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

  1. Первый вариант - специализировать логику создания и обновления: обратитесь к своему классу через какой-либо другой атрибут и вручную напишите поиск для создания, либо задайте ключ, по которому вы обращаетесь, в качестве первичного ключа вашего класса. Вы можете установить имя вашего класса, 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)

Переопределите ваше представление create, чтобы найти связанные классы в атрибутах свободной формы. Кроме того, проверьте, как 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.

Ответ 2

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

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

Ответ 3

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

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

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

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

Мне нужно было установить "contentType", а также "подстроить" мой js-объект.

Ответ 4

Для решения вашей проблемы вы можете использовать этот пакет drf-rw-serializers

Все, что вам нужно сделать, это использовать два сериализатора (один для чтения и один для записи):

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')

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

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

    def create(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().create(validated_data)

    def update(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().update(validated_data)

api_views.py

from drf_rw_serializers import generics

from .models import Exam
from .serializers import WriteExamSerializer, ExamSerializer


class ExamListCreateView(generics.ListCreateAPIView):
    queryset = Exam.objects.all()
    write_serializer_class = WriteExamSerializer
    read_serializer_class = ReadExamSerializer