#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