Сериализатор жалуется на данные запроса - PullRequest
0 голосов
/ 17 апреля 2019

Мне нужно добавить некоторые данные в запрос, поэтому я сделал следующее:

data = {'my_data': 1, **request.data}
...
serializer = MySerializer(data=data)
serializer.is_valid()

Но сериализатор жалуется на неправильные поля:

"Недопустимая строка."

Это говорит то же самое на всех из них. Имеет смысл, потому что я вижу, что это создает диктат, заполненный списками:

{'attr1':[1], 'attr2':[2], ..., 'my_data':1}

Что не имеет смысла, так это то, что это прекрасно работает:

serializer = MySerializer(data=request.data)
serializer.is_valid()

Даже если у объекта QueryDict все поля также заключены в список

Я также попробовал следующее:

data = {'my_data': [1], **request.data}

Но теперь он также жалуется на новое поле.

Что я делаю не так?

Edit:

Просто чтобы уточнить, обходной путь - просто развернуть все элементы:

data = {**{k: v for k, v in request.data.items()}}

Но почему сериализатор ведет себя по-разному с обычными диктовками и с QueryDict с?

Ответы [ 2 ]

0 голосов
/ 18 апреля 2019

Некоторые могут сказать, что возиться с request.data - это не «правильный путь».Но если вы все еще хотите его, то лучше создать изменяемую копию QueryDict:

data = request.data.copy()
data['my_data'] = 1

rest_framework имеет много mixins / generics / builtin-views, которые просто инициализируют сериализаторы как SerializerCls(data=request.data, instance=something_or_none, context={'request': view.request, 'view': view}.И если вы используете один и тот же сериализатор в нескольких представлениях, то лучше сделать ваш сериализатор «умным», а не модифицировать каждое представление.

«Умный» сериализатор должен вычислять все дополнительные данные из «raw request.data / request».«Частично умным» сериализаторам может потребоваться изменить представление.

Если вам просто нужно установить пользовательское значение для некоторого поля модели при сохранении, то вы можете изменить метод представления perform_update / perform_create (если выGenericAPIView -s)

def perform_update(self, serializer):
    serializer.save(my_data=1)

Если вам действительно нужны эти пользовательские данные, учитываемые во время проверки: вы можете использовать контекст:

class MySerializer(...):
    def validate(self, data):
        if 'my_data' in self.context:
            data['my_data'] = self.context['my_data']
        if data.get('my_data'): 
            perform_some_extra_validation(data)
        return data

serializer = MySerializer(data=data, context={'my_data': 1})
serializer.is_valid()
# for ModelView-s you can change context by modifying `get_serializer_context` method

Если вы можете вычислять my_data динамическис request или с request.user, затем:

  • Вы можете сделать это прямо в serializer.validate, используя контекст:
class MySerializer(...):
    def validate(self, data):
        if 'request' in self.context:
            data['my_data'] = build_my_data_from_request(self.context['request'])
        return data

serializer = MySerializer(data=data, context={'request': request})
serializer.is_valid()

# in ModelView-s serializers are already initialized with 'request' in context
  • Или вы можете использовать комбинацию контекста и default arg для поля:


class MyDataDefault(object):
    def set_context(self, serializer_field):
        self.parent = serializer_field.parent

    def __call__(self):
        if 'request' in self.parent.context:
            return build_my_data_from_request(self.parent.context['request'])


class MySerializer(...):
    my_data = serializers.SomeField(default=MyDataDefault())


# if my_data should only be set during creation (not during update)
from rest_framework.serializers import CreateOnlyDefault

class AlternativeMySerializer(...):
    my_data = serializers.SomeField(default=CreateOnlyDefault(MyDataDefault()))


serializer = MySerializer(data=data, context={'request': request})
serializer.is_valid()
# in ModelView-s serializers are already initialized with 'request' in context

Если вы используете ModelViewSet или другие представления, основанные на GenericAPIView, то get_serializer ужеделает что-то похожее на SerializerCls(data=request.data, instance=..., context={'request': request, 'view': self}):

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class()
        kwargs['context'] = self.get_serializer_context()
        return serializer_class(*args, **kwargs)

    ...


    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

0 голосов
/ 18 апреля 2019

QueryDict - это не просто словарь со значениями полей в виде списков, а сложная структура с переопределенными методами, которые определяют его поведение для определенных операторов. Ниже приведен метод getitem, определенный в MultiValueDict , который является базовым классом для QueryDict .

def __getitem__(self, key):
    """
    Return the last data value for this key, or [] if it's an empty list;
    raise KeyError if not found.
    """
    try:
        list_ = super().__getitem__(key)
    except KeyError:
        raise MultiValueDictKeyError(key)
    try:
        return list_[-1]
    except IndexError:
        return []

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

my_query_dict[my_key]

Последний элемент в списке возвращается. Однако, когда вы создаете новый словарь, используя ** на запросе dict, вы получаете просто обычный словарь с его элементами в виде списков, поэтому, когда вы получаете значение ключа, вы получаете список обратно. Для примера, пусть my_query_dict будет экземпляром QueryDict, содержащим значение [1] для ключа 'a'.

my_query_dict['a'] # returns 1
{**my_query_dict}['a'] # returns [1]
...