For my application, I have Locations, and Events given by the following (simplified) models:
class Location(models.Model):
name = models.CharField(max_length=64)
class Event(models.Model):
location = models.ForeignKey(Location)
date = models.DateField()
In my API, I have an endpoint: /api/locations/
which returns all locations, and each location has its events embedded within it, e.g.:
[
{
'id': 1,
'name': 'Location 1',
'events': [
{'id': 1, 'date': '2018-01-01'},
{'id': 2, 'date': '2018-01-14'}
]
},
{
'id': 2,
'name': 'Location 2',
'events': [
{'id': 3, 'date': '2017-12-28'},
{'id': 4, 'date': '2018-01-10'}
]
}
]
my serializers look like:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = ('id', 'date',)
read_only_fields = ('id',)
class LocationSerializer(serializers.ModelSerializer):
events = EventSerializer(many=True)
class Meta:
model = Location
fields = ('id', 'name', 'events',)
read_only_fields = ('id',)
Using ViewSets, I would like to add a filter to this call, with a max_date
, and min_date
parameter, e.g.: /api/locations/?min_date=2018-01-01&max_date=2018-01-15
. Which should return only locations that have events between those two dates, AND should only return the events for each location between those two dates.
In the example data above, both locations would return, Location 1 would have both events listed, but Location 2 would only have the second event in its list. In my ViewSet, I can take those parameters and append them as queryset filters:
class LocationViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = LocationSerializer
def get_queryset(self):
queryset = Location.objects.all()
min_date = self.request.query_params.get('min_date', None)
max_date = self.request.query_params.get('max_date', None)
if min_date is not None and max_date is not None:
queryset = queryset.filter(event__date__lte=max_date, event__date__gte=min_date)
return queryset
Which will filter out any locations that do not have any events between the two dates, however those returned locations show ALL of their events, not just the ones between min_date
and max_date
. I assume I have to somehow pass my query parameters on to the EventSerializer
embedded within LocationSerializer
, but am unsure as to how I might do this.
Try to prefetch filtered events in view:
from django.db.models import Prefetch
class LocationViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = LocationSerializer
def get_queryset(self):
queryset = Location.objects.all()
min_date = self.request.query_params.get('min_date', None)
max_date = self.request.query_params.get('max_date', None)
if min_date is not None and max_date is not None:
queryset = queryset.filter(event__date__lte=max_date, event__date__gte=min_date).prefetch_related(Prefetch('events', queryset=Event.objects.filter(date__lte=max_date, date__gte=min_date))
return queryset
Also you can use serializerMethodField
to filter events:
class LocationSerializer(serializers.ModelSerializer):
events = SerializerMethodField()
class Meta:
model = Location
fields = ('id', 'name', 'events',)
read_only_fields = ('id',)
get_events(self, obj):
min_date = self.context['request'].query_params.get('min_date', None)
max_date = self.context['request'].query_params.get('max_date', None)
events = obj.events.filter(date__lte=max_date, date__gte=min_date)
return LocationSerializer(events, many=True).data
With self.context['request']
you can get request data inside serializer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With