[Django]-Django Rest Framework filter a calculated SerializerMethodField() in ViewSet using filterset_fields

3👍

If you can add a package to you project, it would be easy with django-property-filter

Just convert your serializer code into a property like:

class Activity(BaseModel):
    name = models.CharField(max_length=250)
    last_execution = models.DateTimeField()

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

    @property
    def next_execution(self):
        # all your logic in comment can be pasted here
        return self.last_execution + timedelta(hours=24)  

Remove your SerializerMethodField() and the def get_next_execution from serializer but keep it in fields list, this property will be listed as a field as well

class ActivitySerializer(serializers.ModelSerializer):

    class Meta:
        model = Activity
        fields = ('id', 'name', 'last_execution', 'next_execution',)

Now, in the view, remove the filterset_fields from your ViewSet, we will add it in the FilterSet class…

Create a FilterSet to your ViewSet

from django_property_filter import PropertyFilterSet, PropertyDateFilter

class ActivityFilterSet(PropertyFilterSet):
    next_execution = PropertyDateFilter(field_name='next_execution', lookup_expr='exact') # this is the @property that will be filtered as a DateFilter, you can change the variable name, just keep the `field_name` right

    class Meta:
        model = Activity
        fields = { # here is the filterset_fields property
            'id': ['exact'],
            'name': ['icontains', 'exact'],
            'last_execution': ['exact'],
        }

And then, just add this FilterSet to your ViewSet and the magic happens

class ActivityViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthenticated,)

    serializer_class = ActivitySerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_class = ActivityFilterSet

    def get_object(self):
        return super(ActivityViewSet, self).get_object()

    def get_queryset(self):
        return Activity.objects.all()

0👍

I really don’t like django-filter – here is a method I prefer that gives us (almost) full access to the django-orm from the front end in a few lines of code:

class BaseViewSet(viewsets.ModelViewSet):

    def get_queryset(self):
        queryset = self.queryset
        custom_filter = self.request.query_params.get('filter')
        custom_exclude = self.request.query_params.get('exclude')
    
        if custom_filter: 
            custom_filter = json.loads(custom_filter)
            queryset = queryset.filter(**custom_filter)
        
        if custom_exclude: 
            custom_exclude = json.loads(custom_exclude)
            queryset = queryset.exclude(**custom_exclude)
        
        return queryset

Now we can send requests like this:

// django-like filter:
var filter = {
  last_execution : '2020-01-01 08:20:20'
}

$.ajax({
  url : 'api/activities/?filter=' + JSON.stringify(filter)
  type : 'GET',
  success : function(response){...},
  ...
});

Essentially, your url now looks like:

https://mywebsite.com/api/activities/?filter={"last_execution":"2020-01-01 08:20:20"}

Now we can just add 24 hours to the last_execution_date in our js code to retrieve the activity (instead of doing this on the python side):

// can use libraries like moment.js etc:
var filter = {
  last_execution : '2020-01-02 08:20:20'
}

$.ajax({...});

That’s it! No more adding fields to every – single – viewset.

👤Daniel

Leave a comment