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.
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()