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:
- Django-tables2, an excellent application that allows you to quickly build tables
- Django-filter for easy filtering
- Django-crispy-forms for easy form creation
A view that uses all three apps together could look like this:
from django.views.generic import TemplateView from django_tables2 import RequestConfig # Your write the four classes imported below # (and pick a better location) from foo.models import Foo from foo.models import FooFilter from foo.models import FooTable from foo.models import FooFilterFormHelper 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 your FooFilter (django_filters.FilterSet) 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 (crispy_forms.helper.FormHelper) is added to style and configure the form.
At last the table (django_tables2.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
More
The code above should really be all you need, assuming that you read the app-specific documentation.
A simple table might look like this:
class FooTable(django_tables2.Table): def render_name(self, value, record): url = record.get_absolute_url() return mark_safe('<a href="%s">%s</a>' % (url, record)) class Meta: model = Foo fields = ('name', 'attribute1', )
This is an example for a simple filter:
class FooFilter(django_filters.FilterSet): attribute1 = django_filters.NumberFilter(lookup_type='exact') class Meta: model = Foo fields = ('name', 'attribute1', )
And here's a simple crispy form helper for the filter controls:
class FooFilterFormHelper(crispy_forms.helper.FormHelper): form_method = 'GET' layout = Layout( 'name', 'attribute1', Submit('submit', 'Apply Filter'), )
Rendering and styling of course depends on your projects and preferences. The screenshot uses bootstrap, please refer to the crispy-forms documentation.
I get an error helper object provided to {% crispy %} tag must be a crispy.helper.FormHelper object. any suggestions?