I'm looking for a way to use the serializer context defined in the ModelViewSet using the get_serializer_context to be used in the queryset declaration of a specific 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}
In all serializer methods this is accessible using self.context, but I would like to filter the queryset of this field using this info in the context dictionary:
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)
Here the queryset applied to the StorageLocation (project__code = context['project_code']) is where my current issue lies.
Some additional context: this issue is an attempt to resolve the following error from the rest_framework (the StorageLocation queryset was set to .all()):
projects.models.procurement.StorageLocation.MultipleObjectsReturned: get() returned more than one StorageLocation -- it returned 2!
To do this you will need to create a custom field and override the behavior of either get_queryset
or to_internal_value
. Using get_queryset
is simpler in this case, and keeps all the good validation in the base class, so we'll use that.
This example field uses a VERY generic filter style. I've done it this way so it applies equally to whomever comes after you with a similar question.
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"])
])
Also, you should modify get_serializer_context
to call super()
first and add the new data on top of that.
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx.update(product_code=self.kwargs['product_code'])
return ctx