Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django rest framework- how can I pass query params to foreign key serializer?

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.

like image 814
MarkD Avatar asked Feb 09 '18 16:02

MarkD


1 Answers

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.

like image 196
neverwalkaloner Avatar answered Oct 19 '22 08:10

neverwalkaloner