How to format django-filters using crispy forms?

Viewed 799

Is it possible to format a django_filters filter form with django-crispy-forms? I've been trying to do that, but django_filters.FilterSet doesn't seem to accept the crispy forms formats (from DeviceFilter class). It doesn't give an error either.The only thing that seems to be able to give a format is {{ filter.form|crispy }} but I want to be able to do it in python with FormHelper().

filters.py

from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Row, Column
import django_filters

class DeviceFilter(django_filters.FilterSet):

    device_type = django_filters.ModelChoiceFilter(lookup_expr='exact', field_name='device_type__pk',
                                                   queryset=None)
    device_group = django_filters.ModelChoiceFilter(lookup_expr='exact, field_name='device_group__pk',
                                                   queryset=None)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.filters['device_type'].queryset = DeviceType.objects.filter(owner=self.request.user)
        self.filters['device_group'].queryset = DeviceGroup.objects.filter(owner=self.request.user)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Row(
                Column('device_type', css_class='form-group col-md-6 mb-0'),
                Column('device_group', css_class='form-group col-md-4 mb-0'),
                css_class='form-row'
            ),
            Submit('submit', 'filter')
        )

    class Meta:
        model = Device
        fields = {}

template

<!-- filter -->
{% if filter %}
    <form method="get">
        {{ filter.form|crispy  }}
    </form>

{% endif %}

UPDATE

I share my final solution. This renders this:

enter image description here

class DeviceForm(forms.ModelForm):

    class Meta:
        model = Device
        fields = [
            'device_id',
            'device_type',
            'device_group',
        ]


    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Row(
                Column('device_created', css_class='form-group col-12'),
                css_class='form-row'
            ),
            Row(
                Column('device_id', css_class='form-group col-12'),
                css_class='form-row'
            ),
            Row(
                Column('device_type', css_class='form-group col-6 mb-0'),
                Column('device_group', css_class='form-group col-6 mb-0'),
                css_class='form-row'
            ),
        )

class DeviceFilter(django_filters.FilterSet):

    device_created = django_filters.DateTimeFromToRangeFilter(label='', widget=MyRangeWidget(
        {
            'class': 'datetimepicker form-control',
            'placeholder': 'From'
        }, {
            'class': 'datetimepicker form-control',
            'placeholder': 'To'
        }
    ))


    device_id = django_filters.CharFilter(label='', lookup_expr='icontains', widget=TextInput(attrs={
        'placeholder': 'Enter Device Id'
    }))

    device_group = django_filters.ModelChoiceFilter(label='', lookup_expr='exact',
                                                    field_name='device_group__pk',
                                                    queryset=None, empty_label=('Select Group'))

    device_type = django_filters.ModelChoiceFilter(label='', lookup_expr='exact',
                                                   field_name='device_type__pk',
                                                   queryset=None, empty_label=('Select Type'))


    class Meta:
        form = DeviceForm

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.filters['device_type'].queryset = DeviceType.objects.filter(owner=self.request.user)
        self.filters['device_group'].queryset = DeviceGroup.objects.filter(owner=self.request.user)
2 Answers

Try setting the helper to the form

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # Your code
    self.form.helper = FormHelper()
    # Your code

Or try setting the form in the meta class

class DeviceFilter(django_filters.FilterSet):
    # Your code
    class Meta:
        form = YourCustomFormClass

This Github gist has a really nice solution. The key part is defining a base class (the author calls theirs FilteredSingleTableView) which your table view will inherit from. The base class has a formhelper_class class attribute which children can use to point to a Crispy Form's FormHelper subclass that defines the layout

Here is that portion of their code. I won't re-post the rest since the link is easier to follow

from django_filters.views import FilterView
from django_tables2 import SingleTableMixin


class FilteredSingleTableView(SingleTableMixin, FilterView):
    """Base class for table filtering with crispy forms

    Subclasses should override the formhelper_class
    """

    formhelper_class = None

    def get_filterset(self, filterset_class):
        """Where the magic happens

        Add the helper class to the pre-existing form from django-filters
        """

        kwargs = self.get_filterset_kwargs(filterset_class)
        filterset = filterset_class(**kwargs)
        filterset.form.helper = self.formhelper_class()
        return filterset
Related