Quick reminder what is frisor
Frisor is a web application in Python/Django which is just a box for interesting urls. When using untrusted network I don’t want to login anywhere to save interesting content I found. More about this is here: Introduction to frisor.
This week I introduced filtering for my urls table. I decided to use django-filter for it and I had some issues with integrating it with other third-party libraries I use.
Frisor on github: firsor repo.
Currently filtering in my application looks like this:
Setting up filtering
Setting up filtering with django-filter is quite straight forward. It has to be installed via pip
then added to INSTALLED_APPS
in settings.py
. After that you need to create an equivalent of form
for filtering, manage it in your view and put generated form in your template.
So let’s start from the beginning:
Filtering abstraction
Here is an abstraction for filtering, which will generate a filtering form for me,
it derives from django_filters.FilterSet
:
# filters.py
from django_filters import FilterSet
from .models import Url
class UrlFilter(FilterSet):
class Meta:
model = Url
fields = ['url', 'title', 'tags__name']
UrlFilter
will generate a simple form, which will allow to filter by exact match of tag name,
title or url.
It uses my Url
model and its fields: url
, title
and tags__name
.
This last field contains __
- it’s field lookup which
is used in Django to access subfields of a structure. In my Url
model I’ve
a field tags
- it has type tagulous_models.TagField
, so it has name
field. That’s why in
UrlFilter
form I can access it via tags__name
.
Manage filtering in view
My view which manages filtering, pagination and showing list of urls looks like this:
# views.py
class UrlView(FormView):
# ...
def get_context_data(self, **kwargs):
context = super().get_context_data()
url_list = Url.objects.order_by('-publish_date').all()
url_filter = UrlFilter(self.request.GET, queryset=url_list)
context['url_list'] = self._get_url_page(url_filter.qs,
self.request.GET.get('page'))
context['url_filter'] = url_filter
return context
The most important is url_filter = UrlFilter(self.request.GET, queryset=url_list)
, which
creates an UrlFilter
object which uses filtering parameters from GET
request and list of urls as queryset.
For example for GET
request:
/?url=vevurka.github.io&title=vevurka&tags__name=github
it will contain only urls with url vevurka.github.io, title vevurka and
tag name github. We can access its queryset by url_filter.qs
.
We have to put UrlFilter
object to rendering context to be able to use its data
in template. Now we can set up a template:
Filtering in template
As I’m using django-bootstrap3 for rendering forms I thought it will be simple
to use nice bootstrap form in my template. Because I added url_filter
to
view context, I can access filter form by url_filter.form
:
<!-- index.html -->
<form action="" method="get">
<legend>Filtering</legend>
{% bootstrap_form url_filter.form %}
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
<!-- url list -->
<!-- ... -->
{% bootstrap_pagination url_list %}
That was it, but it happen that it broke my pagination - after changing page, my filter query disappeared (both from form and url)!
After a bit of debugging and looking
into the source of django-bootstrap3 library, I saw that when tag
bootstrap_pagination
creates a links for pages, it uses current GET
request to do it, but
clears other parameters except page one… I don’t know why the designers of django-bootstrap3
decided to do this like this:
# bootstrap3.py from django-bootstrap3 library
def get_pagination_context(page, pages_to_show=11,
url=None, size=None, extra=None,
parameter_name='page'):
# ...
if url:
# Remove existing page GET parameters
url = force_text(url)
url = re.sub(r'\?{0}\=[^\&]+'.format(parameter_name), '?', url)
url = re.sub(r'\&{0}\=[^\&]+'.format(parameter_name), '', url)
But I saw hope in extra
parameter of the same get_pagination_context
function:
# bootstrap3.py from django-bootstrap3 library
def get_pagination_context(page, pages_to_show=11,
url=None, size=None, extra=None,
parameter_name='page'):
# ...
if extra:
if not url:
url = '?'
url += force_text(extra) + '&'
After some googling I came up this solution - adding an extra
parameter to
bootstrap_pagination
tag with all parameters from request:
{% bootstrap_pagination url_list extra=request.GET.urlencode %}
Summing up
So far for me it’s impossible to use any third-party Django libraries without
looking to their source.
Documentation usually doesn’t cover all special cases. On the other side life is to short
to read whole documentation without need … These libraries present different levels of
quality, code sometimes is quite bad or designed in strange way. Don’t expect they
will work well together.
Leave a Comment