Using django-tables2, django-filter and django-crispy-forms together

Using django-tables2, django-filter and django-crispy-forms together

I was recently working on a very CRUDy prototype and decided to use some Django applications and tools together I hadn't combined yet:

A view that uses all three apps together could look like this:

class FooTableView(TemplateView):
    template_name = 'app/foo_table.html'

    def get_queryset(self, **kwargs):
        return Foo.objects.all()

    def get_context_data(self, **kwargs):
        context = super(FooTableView, self).get_context_data(**kwargs)
        filter = FooFilter(self.request.GET, queryset=self.get_queryset(**kwargs))
        filter.form.helper = FooFilterFormHelper()
        table = FooTable(filter.qs)
        RequestConfig(self.request).configure(table)
        context['filter'] = filter
        context['table'] = table
        return context

While this is a basic example there's still a lot going on. The get_context_data() method gets called automatically by the TemplateView and populates the template context with the filter and table objects.

At first the code creates an instance of FooFilter and passes it the request's GET data, and the queryset to work on. The filter does what you'd expect and filters the queryset.

The filter object also includes a form for users to filter the data. A crispy form helper is added to style and configure the form.

At last the table object gets created and configured, based on the filtered queryset and the request data.

Displaying everything on the frontend now becomes as easy as:

{% load django_tables2 crispy_forms_tags %}

{% crispy filter.form filter.form.helper %}
{% render_table table %}

This code should be enough to get you started, assuming that you read the app-specific documentation. Styling everything of course depends on other things in your project, and your preferences.

Pagination and the SingleTableView

The view above is nice, but if you have to paginate your table you might be interested in using a ListView or the SingleTableView that comes with django-tables2.

If you want to use several views like this in your application you would also benefit from using a generic view that takes additional parameters like filter_class and formhelper_class.

Here is an example for such a view based on the SingleTableView. It's not the most robust code, the configuration for example could be optional, get_queryset() can't be modified easily, etc. But if you're still reading you can probably fix whatever issues you find yourself :-)

from django_tables2 import SingleTableView

class PagedFilteredTableView(SingleTableView):
    filter_class = None
    formhelper_class = None
    context_filter_name = 'filter'

    def get_queryset(self, **kwargs):
        qs = super(PagedFilteredTableView, self).get_queryset()
        self.filter = self.filter_class(self.request.GET, queryset=qs)
        self.filter.form.helper = self.formhelper_class()
        return self.filter.qs

    def get_table(self, **kwargs):
        table = super(PagedFilteredTableView, self).get_table()
        RequestConfig(self.request, paginate={'page': self.kwargs['page'],
                            "per_page": self.paginate_by}).configure(table)
        return table

    def get_context_data(self, **kwargs):
        context = super(PagedFilteredTableView, self).get_context_data()
        context[self.context_filter_name] = self.filter
        return context

One thing that changed changed is the RequestConfig line that configures the table's pagination.

As SingleTableView inherits from Django's ListView you automatically get a Paginator object in the template context as paginator.

The get_queryset() method was changed to apply the filter and to return the filtered queryset. That filtered data ends up in the table in get_table() and gets paged. Aftert that it is added to the template context together with the filter.

With this generic view adding more paged filtered crispy table views takes just a few lines of code:

class FooTableView(PagedFilteredTableView):
    model = Foo
    table_class = FooTable
    template_name = 'app/foo_table.html'
    paginate_by = 50
    filter_class = FooFilter
    formhelper_class = FooFilterFormHelper

Published on March 18, 2014 at 7:40 p.m. by Nicolas and tagged development, Django. You can follow the discussion with the comment feed for this post. Feeling generous? Donate!

2 comments

  1. avatar
    wrote this comment on
    I think this is an excellent intro, but your example leaves out how you styled the table to get the filter to display on top of it :(
    Reply to this comment
    1. avatar
      wrote this comment on
      The filter is displayed with the {% crispy %} template tag, that's all there is to it.
      Reply to this comment

Start a new thread

Cancel reply
Markdown. Syntax highlighting with <code lang="php"><?php echo "Hello, world!"; ?></code> etc.