Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

Splitting a query into individual fields inDjango

$
0
0

As you should have already seen inprevious articles, I really like using django-filter since it covers (nearly) all my queryset filtering needs. With django-filter, you define a bunch of fields and it will automatically create inputs for each one of these fields so that you can filter by each one of these fields individually or a combination ofthem.

However, one thing that django-filter (and django in generally) lacks is the ability to filter multiple fields using a single input . This functionality may be familiar to some readers from the datatable jquery plugin. If you take a look at the example in the datatable homepage, you’ll see a single “Search” field. What is really great is that you can enter multiple values (seperated by spaces) into that field and it will filter the individual table values by each one of them. For example, if you enter “2011 Engineer” you’ll see all engineering positions that started on 2011. If you append “Singapore” (so you’ll have “2011 Engineer Singapore”) you’ll also get only the correspondingresults!

This functionality is really useful and is very important to have if you use single-input fields to query your data. One such example is if you use autocompletes, for example with django-autocomplete-light: You’ll have a single input however you may need to filter on more than one field to find out yourselection.

In the following ost I’ll show you how to implement this functionality using Django and django-filters (actually django-filters will be used to provide the form) - to see it in action you may use the https://github.com/spapas/django_table_filtering repository (check out the /filter_ex/ view).

I won’t go into detail on how the code is structured (it’s really simple) and I’ll go directly to the filter I am using. Instead of using a filter you can of course directly query on your view. What you actually needis:

a queryset with the instances you want tosearch a text value with the query (that may containspaces) a list of the names of the fields you want tosearch

In my case, I am using a Book model that has the following fields: id, title, author, category . I have created a filter with a single field named ex that will filter on all these fields. So you should be able to enter “King It” and find “It by Stephen King”. Let’s see how the filter isimplemented:

import itertools class BookFilterEx(django_filters.FilterSet): ex = django_filters.MethodFilter() search_fields = ['title', 'author', 'category', 'id', ] def filter_ex(self, qs, value): if value: q_parts = value.split() # Permutation code copied from http://stackoverflow.com/a/12935562/119071 list1=self.search_fields list2=q_parts perms = [zip(x,list2) for x in itertools.permutations(list1,len(list2))] q_totals = Q() for perm in perms: q_part = Q() for p in perm: q_part = q_part & Q(**{p[0]+'__icontains': p[1]}) q_totals = q_totals | q_part qs = qs.filter(q_totals) return qs class Meta: model = books.models.Book fields = ['ex'] The meat of this code is in the filter_ex method, let’s analyze it line by line: First of all, we split the value to its corresponding parts using the whitespace to sperate into individual tokens. For example if the user has entered King It , q_parts be equal to ['King', 'It'] . As you can see the search_fields attribute contains the names of the fields we want to search. The first thing I like to do is to generate all possible combinations between q_parts and search_fields , I’ve copied the list combination code from http://stackoverflow.com/a/12935562/119071 and it is the line perms = [zip(x,list2) for x in itertools.permutations(list1,len(list2))] . The itertools.permutations(list1,len(list2)) will generate all permutations of list1 that have length equal to the length of list2. I.e if list2 is ['King', 'It'] (len=2) then it will generate all combinations of search_fields with length=2, i.e it will generate the following list oftuples: [ ('title', 'author'), ('title', 'category'), ('title', 'id'), ('author', 'title'), ('author', 'category'), ('author', 'id'), ('category', 'title'), ('category', 'author'), ('category', 'id'), ('id', 'title'), ('id', 'author'), ('id', 'category') ] Now, the zip will combine the elements of each one of these tuples with the elements of list2 , so, in our example ( list2=['King', 'It'] ) perms will be the followingarray: [ [('title', 'King'), ('author', 'It')], [('title', 'King'), ('category', 'It')], [('title', 'King'), ('id', 'It')], [('author', 'King'), ('title', 'It')], [('author', 'King'), ('category', 'It')], [('author', 'King'), ('id', 'It')], [('category', 'King'), ('title', 'It')], [('category', 'King'), ('author', 'It')], [('category', 'King'), ('id', 'It')], [('id', 'King'), ('title', 'It')], [('id', 'King'), ('author', 'It')], [('id', 'King'), ('category', 'It')] ]

Notice that itertools.permutations(list1,len(list2)) will return an empty list if len(list2) > len(list1) - this is actually what we want since that means that the user entered more query parts than the available fields, i.e we can’t match each one of the possible values after we split the input with a search field so we should returnnothing.

Now, what I want is to create a single query that will combine the tuples in each of these combinations by AND (i.e title==King AND author==It ) and then combine all these subqueries using OR (i.e “ (title==King AND author==It) OR (title==King AND category==It) OR (title==King AND id==It) OR …“.

This could of course be implemented with a raw sql query however we could use some interesting django tricks for this. I’ve already done something similar toa previous article so I won’t go into much detail explaining the code that creates the q_totals Q object. What it does is that it create a big django Q object that combines using AND ( & ) all individual q_part objects. Each q_part object combines using OR ( | ) the individual combinations of field name and value ― I’ve used __icontains` to create the query. So the result will be something likethis:

q_totals = Q(title__icontains='King') & Q(author__icontains='It') | Q(title__icontains='King') & Q(category__icontains='It') | Q(title__icontains='King') & Q(id__icontains='It') | Q(author__icontains='King') & Q(title__icontains='It') ...

Filtering by this q_totals will return the correctvalues!

One extra complication we should be aware of is what happens if the user needs to also search for boo

Viewing all articles
Browse latest Browse all 9596

Trending Articles