AncientArtOfDesign
AncientArtOfDesign

Reputation: 65

Django Filter - how to call a single method for any combination of filter?

I'd like all my filters to route to a single method, so I can look at each and decide logic based on which ones are set. I have this working, but each filter is now calling the same method once, leading below code to print hi 4 times.

Is there a best practice way to route all filters to a single method? I'd like hi to be only printed once here, and wondering if I can avoid using a hacky solution.

class CityFilter(FilterSet):
    start_date_i = django_filters.DateTimeFilter(field_name='dept_date', method='filter_city')
    start_date_j = django_filters.DateTimeFilter(field_name='dept_date', method='filter_city')
    end_date_i = django_filters.DateTimeFilter(field_name='ret_date', method='filter_city')
    end_date_j = django_filters.DateTimeFilter(field_name='ret_date', method='filter_city')

    class Meta:
        model = models.City

    def filter_city(self, queryset, name, value):
        print('hi')
        data = self.data
        sdate_i = data.get('start_date_i')
        sdate_j = data.get('start_date_j')
        edate_i = data.get('end_date_i')
        edate_j = data.get('end_date_j')

        # do expensive stuff and build queryset from all filter name / values

        return queryset

Upvotes: 0

Views: 843

Answers (1)

Abdul Aziz Barkat
Abdul Aziz Barkat

Reputation: 21822

You can try combining the fields and hence the filter into one using the MultiValueField [Django docs] and SuffixedMultiWidget [django-filter docs]:

from django import forms
from django_filters import Filter
from django_filters.widgets import SuffixedMultiWidget

class QuadDateTimeWidget(SuffixedMultiWidget):
    '''A widget that combines 4 `DateTimeInput` widgets'''
    suffixes = ['start_i', 'start_j', 'end_i', 'end_j']
    
    def __init__(self, attrs=None):
        widgets = [
            forms.DateTimeInput(),
            forms.DateTimeInput(),
            forms.DateTimeInput(),
            forms.DateTimeInput(),
        ]
        super().__init__(widgets, attrs):
    
    def decompress(self, value):
        # Need a list of values to be able to pass to children widgets
        if value:
            value = list(value)
            value.extend([None] * (4 - len(value)))
            return value
        return [None, None, None, None]


class QuadDateTimeField(forms.MultiValueField):
    '''A form field that uses 4 `DateTimeField` instances'''
    widget = QuadDateTimeWidget
    
    def __init__(self, fields=None, *args, **kwargs):
        if fields is None:
            fields = (
                forms.DateTimeField(),
                forms.DateTimeField(),
                forms.DateTimeField(),
                forms.DateTimeField()
            )
        super().__init__(fields, *args, **kwargs)
    
    def compress(self, data_list):
        # You can change this to a dictionary for simpler use if needed, although remember to change `decompress` in the widget above to return a list of the values in that case
        if data_list:
            return list(data_list)
        return [None, None, None, None]


class QuadDateTimeFilter(Filter):
    '''A filter that uses QuadDateTimeField'''
    field_class = QuadDateTimeField
    
    def filter(self, qs, value):
        # You can also filter here if needed, you can get the `field_name` parameter by using `self.field_name`
        raise NotImplementedError("Implement the method on the FilterSet class")

class CityFilter(FilterSet):
    date = QuadDateTimeFilter(field_name='dept_date', method='filter_city')
    
    def filter_city(self, queryset, name, value):
        print('hi')
        sdate_i = value[0]
        sdate_j = value[1]
        edate_i = value[2]
        edate_j = value[3]
        
        # do expensive stuff and build queryset from all filter name / values
        
        return queryset

Upvotes: 1

Related Questions