Allee Clark
Allee Clark

Reputation: 149

Django rest filter custom fields

I am converting a UNIX date to a string date and passing it as a custom read-only field. What would be the best way to use django-filter to be able to filter this custom field? The error I get is Cannot resolve keyword 'convert_time' into the field. Choices are:

Models class

class AccountT(models.Model):
    created_date_t = models.BigIntegerField(blank=True, null=True)
    def convert_time(self):
        result = time.strftime("%D", time.localtime(self.created_date_t))
        return result

Serializer Class

class AccountTSerializer(serializers.ModelSerializer):
    created_date = serializers.ReadOnlyField(source='convert_time')
    class Meta:
        model = AccountT
        fields = ('othermodelfield','othermodelfield', 'created_date',)

ListAPIView

class AccountTListView(generics.ListAPIView):
    serializer_class = AccountTSerializer
    queryset = AccountT.objects.all()
    filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter,)
    filter_fields = ('othermodelfield','created_date_t')

Upvotes: 3

Views: 8846

Answers (3)

Sherpa
Sherpa

Reputation: 1998

The filterset_fields option is a shortcut that inspects model fields (not serializer fields) in order to generate filters. Since created_date isn't a model field, you'll need to manually declare a filter on a filterset_class. Declared filters can take advantage of the method argument, which will allow you to transform the incoming date into your timestamp. Something like...

# filters.py
from django_filters import rest_framework as filters

class AccountTFilter(filters.FilterSet):
    # could alternatively use IsoDateTimeFilter instead of assuming local time.
    created_date = filters.DateTimeFilter(name='created_date_t', method='filter_timestamp')

    class Meta:
        model = models.AccountT
        # 'filterset_fields' simply proxies the 'Meta.fields' option
        # Also, it isn't necessary to include declared fields here
        fields = ['othermodelfield']

    def filter_timestamp(self, queryset, name, value):
        # transform datetime into timestamp
        value = ...

        return queryset.filter(**{name: value})

# views.py
class AccountTListView(generics.ListAPIView):
    filterset_class = filters.AccountTFilter
    ...

Note: The old filter_* options have since been renamed to filtserset_*.

Upvotes: 5

Lance
Lance

Reputation: 995

To achieve this I would create a custom field on the serializer (ConvertTimeField):

import time

from .models import AccountT
from rest_framework import serializers


# Custom serializer field
# https://www.django-rest-framework.org/api-guide/fields/#custom-fields
class ConvertTimeField(serializers.ReadOnlyField):
    """
    Convert time Field.
    """

    def to_representation(self, value):
        return time.strftime("%D", time.localtime(value))

    def to_internal_value(self, data):
        """
        Here is where you could convert the incoming value
        (It's only needed if you want to perform modification before writing to the database)
        """


# Serializer
class AccountTSerializer(serializers.ModelSerializer):
    created_date = ConvertTimeField(source='created_date_t')

    class Meta:
        model = AccountT
        fields = ('othermodelfield', 'othermodelfield', 'created_date')

NOTE: You still need to pass your filter argument in the same format i.e UNIX epoch when you filter. If you need to change that you can convert your filter query param as suggested here: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-query-parameters. (Although, there are also other ways you could try accomplish that)

Upvotes: 0

Ykh
Ykh

Reputation: 7717

class AccountT(models.Model):
    created_date_t = models.BigIntegerField(blank=True, null=True)
    created_date = models.DateField(null=True)

    def save(self, *args, **kwargs):
        self.created_date = self.convert_time()
        return super(IncomeExpense, self).save(*args, **kwargs)

    def convert_time(self):
        result = time.strftime("%D", time.localtime(self.created_date_t))
        return result


class AccountTListView(generics.ListAPIView):
    serializer_class = AccountTSerializer
    queryset = AccountT.objects.all()
    filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter,)
    filter_fields = ('othermodelfield','created_date')

class AccountTFilter(FilterSet):
    class Meta:
        model = AccountT
        fields = {
            'created_date': ['gte', 'lte'],
        }

class AccountTListView(generics.ListAPIView):
    serializer_class = AccountTSerializer
    queryset = AccountT.objects.all()
    filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter,)
    filter_class = AccountTFilter

Upvotes: 0

Related Questions