Использование контекста сериализатора в определении набора запросов поля сериализатора

#python #django-models #serialization #django-rest-framework #drf-queryset

#python #django-модели #сериализация #django-rest-framework #drf-набор запросов

Вопрос:

Я ищу способ использовать контекст сериализатора, определенный в ModelViewSet, используя get_serializer_context, который будет использоваться в объявлении набора запросов определенного SlugRelatedField:

 class ReservationViewSet(ViewPermissionsMixin, viewsets.ModelViewSet):
serializer_class = ReservationSerializer

def get_queryset(self):
    code = self.kwargs['project_code']
    project= Project.objects.get(code=code)
    queryset = Reservation.objects.filter(project=project)
    return queryset

def get_serializer_context(self):
    return {"project_code": self.kwargs['project_code'], 'request': self.request}
 

Во всех методах сериализатора это доступно с помощью self.context, но я хотел бы отфильтровать набор запросов этого поля, используя эту информацию в словаре контекста:

 class ReservationSerializer(serializers.ModelSerializer):

    project= serializers.SlugRelatedField(slug_field='code', queryset=Project.objects.all(), required=False)
    storage_location = serializers.SlugRelatedField(slug_field='description', queryset=StorageLocation.objects.filter(project__code = context['project_code'])), required=False)
 

Здесь набор запросов, примененный к местоположению хранилища (project__code = context[‘project_code’]), — вот в чем заключается моя текущая проблема.

Некоторый дополнительный контекст: эта проблема является попыткой устранить следующую ошибку из rest_framework (для набора запросов StorageLocation было установлено значение .all()):

проекты.модели.закупки.Расположение хранилища.MultipleObjectsReturned: get() вернул более одного места хранения — он вернул 2!

Ответ №1:

Для этого вам нужно будет создать настраиваемое поле и переопределить поведение либо get_queryset или to_internal_value . get_queryset В этом случае использование проще, и вся правильная проверка сохраняется в базовом классе, поэтому мы будем использовать это.

В этом примере поля используется ОЧЕНЬ общий стиль фильтра. Я сделал это таким образом, чтобы это в равной степени относилось к любому, кто придет к вам с подобным вопросом.

 from typing import Optional, List
from rest_framework.relations import SlugRelatedField


class CustomSlugRelatedField(SlugRelatedField):
    """
    Generic slug related field, with additional filters.
    Filter functions take (queryset, context) and return a queryset

    >>> class MySerializer:
    >>>    field = CustomSlugRelatedField(ModelClass, 'slug', filters=[
    >>>        lambda qs, ctx: qs.filter(field=ctx["value"])
    >>>    ])
    """

    def __init__(self, model, slug_field: str, filters: Optional[List] = None):
        assert isinstance(filters, list) or filters is None
        super().__init__(slug_field=slug_field, queryset=model.objects.all())
        self.filters = filters or []

    def get_queryset(self):
        qs = super().get_queryset()
        for f in self.filters:
            qs = f(qs, self.context)
        return qs


class MySerializer(serializers.Serializer):
    field = CustomSlugRelatedField(Product, 'slug', filters=[
        lambda q, c: q.filter(product_code=c["product_code"])
    ]) 

 

Кроме того, вы должны изменить get_serializer_context вызов super() на первый и добавить новые данные поверх этого.

     def get_serializer_context(self):
        ctx = super().get_serializer_context()
        ctx.update(product_code=self.kwargs['product_code'])
        return ctx
 

Комментарии:

1. Спасибо, Эндрю, ваш общий пример уже многое объясняет: если я прав, контекст dict передается из сериализатора в его поля, где его можно использовать для создания требуемых запросов путем изменения метода get_queryset нашего пользовательского slugrelatedfield. в этом случае я также могу использовать django.db.models. Вопрос: как передать пользовательские фильтры в контекст и применить их здесь?

2. Вы можете фильтровать набор запросов, как хотите, да. Мое мнение заключается в том, чтобы выполнять фильтрацию в фильтре, а не передавать предварительно созданные Q объекты в контексте.

Ответ №2:

Спасибо, Эндрю, проблема решена:

  • Поскольку это довольно часто повторяющийся шаблон в наших сериализаторах, ваш метод пользовательского поля является самым чистым (с небольшими упрощениями, делающими его немного менее общим)

Основываясь на вашем решении, я также нашел этот способ, изменив метод ‘get_fields’ сериализатора. Менее сложный, но и менее чистый, если шаблон встречается довольно часто:

 class ReservationSerializer(serializers.ModelSerializer):

def get_fields(self, *args, **kwargs):
    fields = super().get_fields(*args, **kwargs)
    fields['storage_location'].queryset = StorageLocation.objects.filter(project__code=self.context['project_code'])
    return fields