Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a SerializerMethodField that calculates the next execution date adding hours to the last execution time

Simplifying the code, a have this model:

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

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('name',)

And to send to front-end the next excution time, I have this SerializerMethodField() in my Serializer

class ActivitySerializer(serializers.ModelSerializer):
    next_execution = serializers.SerializerMethodField('get_next_execution')

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

    def get_next_execution(self, data):
        # last = ActivityExecute.objects.filter(activity_id=data.id).order_by('-executed_at').first()
        # time = data.periodicity_type.time_in_hour
        # if last:
        #     if data.equipment.hour_meter > -1:
        #         return last.executed_at + timedelta(hours=time - (data.equipment.hour_meter - last.hour_meter))
        #     return last.executed_at + timedelta(hours=time)
        # 
        # return data.equipment.created_at + timedelta(hours=time)
        return data.last_execution + timedelta(hours=24)

But when I try to add the calculated field to filterset_fields like this:

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

    serializer_class = ActivitySerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filterset_fields = {
        'id': ['exact'],
        'name': ['icontains', 'exact'],
        'last_execution': ['exact'],
        'next_execution': ['exact'],
    }

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

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

I got this error: 'Meta.fields' must not contain non-model field names: next_execution

There is a way to add the SerializerMethodField() to filterset_fields? Use the method get_queryset will make all my logic in that calculated field (commented) be duplicated. The field works fine, the problem is only to filter the value by the front-end using the query param.

like image 1000
Zero Avatar asked Dec 17 '25 21:12

Zero


1 Answers

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()
like image 180
Bruno Luiz K. Avatar answered Dec 20 '25 10:12

Bruno Luiz K.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!