2015-08-01 3 views
1

Я сталкиваюсь с странным поведением при написании вложенных структур с помощью django-rest, а затем пытается их протестировать с помощью тестового клиента django-rest. Вложенный дочерний объект должен быть необязательным.Сериализация необязательно вложенных структур: разница между QueryDict и нормальным dict?

Вот пример сериализатору:

from rest_framework import serializers 

class OptionalChildSerializer(serializers.Serializer): 
    field_b = serializers.IntegerField() 
    field_c = serializers.IntegerField() 

    class Meta: 
     fields = ('field_b', 'field_c',) 

class ParentSerializer(serializers.Serializer): 
    field_a = serializers.IntegerField() 
    child = OptionalChildSerializer(required=False, many=False) 

    class Meta: 
     fields = ('a', 'child',) 

    def perform_create(self, serializer): 
     # TODO: create nested object. 
     pass 

(я опустил код в perform_create, так как это не имеет отношения к вопросу).

Теперь, проходя нормальный Dict в качестве аргумента данных работает просто отлично:

ser = ParentSerializer(data=dict(field_a=3)) 
ser.is_valid(raise_exception=True) 

Но проходя QueryDict вместо этого будет терпеть неудачу:

from django.http import QueryDict 
ser = ParentSerializer(data=QueryDict("field_a=3")) 
ser.is_valid(raise_exception=True) 

ValidationError: {'child': {'field_b': [u'This field is required.'], 'field_c': [u'This field is required.']}} 

На фактического веб-сайта, то API получает нормальный dict и, следовательно, отлично работает. Однако во время тестирования использование чего-то вроде client.post('url', data=dict(field_a=3)) приведет к тому, что QueryDict будет передан в представление и, следовательно, не будет работать.

Так что мой вопрос: в чем разница между QueryDict и нормальным dict? Или я подхожу к этому неправильно?

+0

В данных = QueryDict ("field_a = 3") следует указывать "field_a = 3"? Его не цитируется в data = dict (field_a = 3) –

+1

Вставьте код тестового кода. Каков ваш запрос «content_type»? –

+0

А, я не установил его, но я думаю, это делает его multipart/form. Я ожидал, что django-rest предоставит приложение/json автоматически. Это, конечно, откуда мой QueryDict. – Nuschk

ответ

5

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

request.data обычно будет QueryDict или нормальным словарем в зависимости от анализатора, используемого для анализа содержимого запроса.

  • JSONParser:

он анализирует содержание запроса JSON-то есть содержание с .media_type как application/json.

  • FormParser

Он анализирует содержимое HTML формы. Здесь request.data заполнен данными QueryDict. У них есть .media_type как application/x-www-form-urlencoded.

  • MultiPartParser

Он разбирает многослойную содержимое HTML формы, который поддерживает загрузку файлов. Здесь также request.data заселен QueryDict. Они имеют .media_type как multipart/form-data.

  • FileUploadParser

Он анализирует содержание загрузки сырого файла. Свойством request.data является словарь с одним ключом file, содержащий загруженный файл.

Как DRF determines анализатор?

Когда ФПИ обращается к request.data, он анализирует заголовок Content-Type на входящий запрос, а затем определяет, какой анализатор использовать для разбора содержимого запроса.

Вам нужно будет установить заголовок Content-Type при передаче данных в противном случае он будет использовать либо многочастный или форму парсер для синтаксического анализа содержания запроса и дать вам в request.data вместо словаря с QueryDict.

Согласно DRF документации,

Если вы не установили тип контента, большинство клиентов будет использовать по умолчанию 'application/x-www-form-urlencoded', который не может быть то, что вы хотели.

Так при отправке JSon кодированные данные, а также установить заголовок Content-Type в application/json, а затем она будет работать, как ожидалось.

Почему request.data иногда QueryDict, а иногда и dict?

Это сделано, потому что разные кодировки имеют разные типы данных и свойства.

Например, данные формы - это кодировка, которая поддерживает несколько ключей одного и того же значения, тогда как json не поддерживает это.

Кроме того, в случае данных JSON, request.DATA не может быть dict вообще, это может быть список или любой другой примитив json.

Отъезд Google Groups thread примерно такой же.

Что вам нужно сделать?

Вы можете добавить format='json' в тесте, когда POSTing данные, которые будут устанавливать тип содержимого, а также правильно сериализуют данные.

client.post('url', format='json', data=dict(field_a=3)) 

Вы также можете отправить JSON-кодированное содержимое с content-type аргументом.

client.post('url', json.dumps(dict(field_a=3)), content_type='application/json') 
+1

Спасибо за подробный ответ, который вызывает мою проблему. Для дополнительной справки вы можете добавить, что добавление 'format = 'json'' в тестах задает тип содержимого, а также правильно сериализует данные. – Nuschk

+0

Обновлен анс! –

+1

Отлично, спасибо! – Nuschk

0

Клиент тестирования django-rest не выполняет автоматическую сериализацию данных как json, но использует multipart/form, что приводит к QueryDict.

Существует, однако, формат, описанный in the docs. Следующий тестовый код работает отлично:

client.post('url', format='json', data=dict(field_a=3)) 

Я все еще озадачен на различном поведении сериализатора между нормальным Dict и QueryDict, хотя ...

Спасибо Раджеш указал мне в правильном направлении!