2016-12-24 3 views
6

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

Вот мои 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.".

+0

Это обычная проблема, посмотрите на мой ответ, вы найдете это полезным. http://stackoverflow.com/questions/41308406/django-rest-framework-add-object-to-request-data-and-then-call-serializer-is-va –

ответ

15

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

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

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

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

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

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

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

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

Override ваш вид создается для поиска связанных классов по атрибутам свободной формы. Кроме того, вы можете посмотреть how DRF implements this with mixins. Вы также должны переопределить метод 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) 

Тогда specify the serializer you wish to use for different operations для Viewset. Здесь мы возвращаем вложенные Subject и класс данных для операций чтения, но только использовать их первичные ключи для операций обновления (намного проще):

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 в чистом виде.

+0

Большое вам спасибо, мужик! Это хороший подход, который я буду использовать для дальнейших проектов. – wencakisa

+0

Я рад, если это вам помогло :) У веселого Рождества! –

+0

С Рождеством! – wencakisa

0

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

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

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

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

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