/var/

Various programming stuff

Hello! If you are using an ad blocker but find something useful here and want to support me please consider disabling your ad blocker for this site.

Thank you,
Serafeim

A pandas pivot_table primer

Notebook

Splitting a query into individual fields in Django

As you should have already seen in previous 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 of them.

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 corresponding results!

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 your selection.

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 need is:

  • a queryset with the instances you want to search
  • a text value with the query (that may contain spaces)
  • a list of the names of the fields you want to search

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 is implemented:

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 of tuples:

[
    ('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 following array:

[
    [('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 return nothing.

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 to a 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 like this:

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 correct values!

One extra complication we should be aware of is what happens if the user needs to also search for books with multiple words in their titles. For example, if the user enters “Under the Dome King” or “It Stephen King” or even “The Stand Stephen King” we won’t get any results :(

To fix this, we need to get all possible combinations of sequential substrings, i.e for “Under the Dome King”, after we split it to [‘Under’, ‘the’, ‘Dome’, ‘King’] we’ll need the following combinations:

[
    ['Under', 'the', 'Dome', 'King'],
    ['Under', 'the', 'Dome King'],
    ['Under', 'the Dome', 'King'],
    ['Under', 'the Dome King'],
    ['Under the', 'Dome', 'King'],
    ['Under the', 'Dome King'],
    ['Under the Dome', 'King'],
    ['Under the Dome King']
]

A possible solution for that problem can be found on this SO answer: http://stackoverflow.com/a/27263616/119071.

Now, to extend our solution to include this, we’d need to actually search for each one of the above possiblities and combine again the results with OR, something like this:

def filter_ex(self, qs, value):
    if value:
        q_parts = value.split()

        # Use a global q_totals
        q_totals = Q()

        # This part will get us all possible segmantiation of the query parts and put it in the possibilities list
        combinatorics = itertools.product([True, False], repeat=len(q_parts) - 1)
        possibilities = []
        for combination in combinatorics:
            i = 0
            one_such_combination = [q_parts[i]]
            for slab in combination:
                i += 1
                if not slab: # there is a join
                    one_such_combination[-1] += ' ' + q_parts[i]
                else:
                    one_such_combination += [q_parts[i]]
            possibilities.append(one_such_combination)

        # Now, for all possiblities we'll append all the Q objects using OR
        for p in possibilities:
            list1=self.search_fields
            list2=p
            perms = [zip(x,list2) for x in itertools.permutations(list1,len(list2))]

            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

The previous filtering code works fine with querise like “The Stand” or “Under the Dome Stephen King”!

One thing that you must be careful is that this code will create very complicated and big queries. For example, searching for “Under the Dome Stephen King” will result to q_totals getting this monster value:

(OR:
(AND: ),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), 'title__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome Stephen'),('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('title__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under'), ('author__icontains', u'the Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('category__icontains', u'the Dome Stephen King')),
(AND: ('title__icontains', u'Under'), ('id__icontains', u'the Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('title__icontains', u'the Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('category__icontains', u'the Dome Stephen King')),
(AND: ('author__icontains', u'Under'), ('id__icontains', u'the Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('title__icontains', u'the Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('author__icontains', u'the Dome Stephen King')),
(AND: ('category__icontains', u'Under'), ('id__icontains', u'the Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('title__icontains', u'the Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('author__icontains', u'the Dome Stephen King')),
(AND: ('id__icontains', u'Under'), ('category__icontains', u'the Dome Stephen King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome'), ('category__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('author__icontains', u'Under the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('category__icontains', u'Under the'), ('id__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under the'), ('title__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under the'), ('author__icontains', u'Dome Stephen King')),
(AND: ('id__icontains', u'Under the'), ('category__icontains', u'Dome Stephen King')),
(AND: ('title__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome'), ('id__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('title__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('author__icontains', u'Stephen'), ('category__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome'), ('category__icontains', u'Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('author__icontains', u'Under the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('category__icontains', u'Under the Dome'), ('id__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the Dome'), ('title__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the Dome'), ('author__icontains', u'Stephen King')),
(AND: ('id__icontains', u'Under the Dome'), ('category__icontains', u'Stephen King')),
(AND: ('title__icontains', u'Under the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('title__icontains', u'Under the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome Stephen'), ('category__icontains', u'King')),
(AND: ('author__icontains', u'Under the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('category__icontains', u'Under the Dome Stephen'), ('id__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome Stephen'), ('title__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome Stephen'), ('author__icontains', u'King')),
(AND: ('id__icontains', u'Under the Dome Stephen'), ('category__icontains', u'King')),
('title__icontains', u'Under the Dome Stephen King'),
('author__icontains', u'Under the Dome Stephen King'),
('category__icontains', u'Under the Dome Stephen King'),
('id__icontains', u'Under the Dome Stephen King')
)

This query has around 200 different OR parts!!! So please be careful on the amount of search fields you’ll enable to works with this method or your database will really struggle!

How to download all images of an imgur album

Recently I stubmled upon a great imgur album that contained 379 movie stills that could be used for desktop background. I really liked the idea and wanted to download all the images in order to put them in a folder and use them as a slideshow for my Windows desktop background.

Downloading them one by one would be considered penal labour so I tried to find out an automatic way to get them all. With some research in google, I found out an old post with the hint that by appending /zip to the URL you could get a zip with all the images — this didn’t work for me. I also tried various browser tools for scrapping or downloading all images from a page but they didn’t work also (they could only download a small number of the images and not all).

This seemed strange to me until I understood how imgur loads its images by “inspecting” an image and taking a look at the page’s DOM structure through the console:

How imgur loads images

As we can see, the imgur client-side code has a component with a post-images class that contains the visible images (and thoese that are above/below the visible images). When the user scrolls up/down the contents of post-images will be changed accordingly (notice how the component with id=EKMGEPc moves down when I scroll up). What this means is that each time there are 3-4 images (this actually depends on your window size) under post-images that are changed when you scroll — that’s why downloaders / scrappers are not working (since these tools just inspect the DOM they only see these 3-4 images to download).

Another interesting observation is that if you take a look at the network tab when you scroll app down you won’t see any ajax calls (the only network calls are the images that are downloaded when they are appended to the DOM). So this means that somewhere there’s an array that is loaded when the page is loaded and contains all the images of the album. If we can access this array then we’d be able to get all the URLs of the images…

From a quick look at the DOM structure we can understand that this is a React application (components have a data-reactid attribute). So I tried the React Developer Tools extension to see if I could find anything insteresting. Here’s the output:

Imgur - react dev tools

As you can see, there seem to be 4 top-level react elements — the interesting one is GalleryPost. If you take a look at its props (in the right hand side of the react-devtools) you’ll see that it has an album_image_store property which also seems interesting (it should be the image store for this album). After searching a bit its attributes you’ll see that it has a _ child attribute, which has a posts child attribute which has an aoi3T attribute (notice that this is similar to the URL id of the album) and, finally this has an images attribute with objects describing all the images of that album \o/!

Now we need to get our hands on that images array contents. Unfortunately, right clicking doesn’t seem to do anything from react-dev-tools and there doesn’t seem a way to copy data from that panel… However, in the upper right position of that window you’ll see the hint ($r in the console) which means that the selected react component is available as $r in the normal javascript console - so by entering

copy($r.props.album_image_store._.posts.aoi3T.images)

I was able to copy the images of the album to my clipboard (please notice that $r will have the value of the selected react component so, before trying it you must select the GalleryPost component in the react-dev-tools tab)!

I dumped this to a file to take a look at it - it is really easy to interpret it:

[
  {
    "hash": "MQplfkV",
    "title": "2001: A Space Odyssey",
    "description": "Cinematographer: Geoffrey Unsworth\n\nsource:\nhttp://www.filmcaptures.com/2001-a-space-odyssey/",
    "width": 1920,
    "height": 864,
    "size": 2262862,
    "ext": ".png",
    "animated": false,
    "prefer_video": false,
    "looping": false,
    "datetime": "2014-10-25 04:02:58",
    "thumbsize": "g",
    "minHeight": 306,
    "shown": true,
    "containerHeight": 501
  },
..

The imgur images have a URL of http//i.imgur.com/{hash}{ext} so, we can use the following small python 2 program to download all images from that album:

import requests
import json
from slugify import slugify

# Modified from http://stackoverflow.com/a/16696317/119071
def download_file(url, local_filename):
    r = requests.get(url, stream=True)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
                #f.flush() commented by recommendation from J.F.Sebastian
    return local_filename


if __name__ == '__main__':
    for i, jo in enumerate(json.loads(open("album.txt").read())):
        filename = '{0}-{1}{2}'.format(slugify(jo['title']), i+1, jo['ext'])
        url = 'http://i.imgur.com/{0}{1}'.format(jo['hash'].strip(), jo['ext'])
        print filename, url
        download_file(url, filename)

Notice that the above uses the requests library to retrieve the files and the python-slugify library to generate a filename using the image title so these libraries must be installed by using pip install requests python-slugify. This will read a file named album.txt that should contain the copied imgur album images in the same directory and download all the images.

Disclaimer The above methodology works today (27-06-2016) - probably it will stop working sometime in the future, when imgur changes its image loading algorithm or its image object representation. Also, I haven’t been able to find a way to quickly access the GalleryPost react component from the javascript console - you need to install the react dev tools and select that component from there so that you’ll have the $r reference to it in the javascript console. Finally, don’t forget to change the copy($r.props.album_image_store._.posts.aoi3T.images) depending on your album id (also if the id is not a valid identifier, for example it starts with number, use copy($r.props.album_image_store._.posts['aoi3T'].images).

Using Werkzeug debugger with Django

Introduction

Werkzeug is a WSGI utility library for Python. Beyond others, it includes an interactive debugger - what this means is that when your python application throws an exception, Werkzeug will display the exception stacktrace in the browser (that’s not a big deal) and allow you to write python commands interactively wherever you want in that stacktrace (that’s the important stuff).

Now, the even more important stuff is that you can abuse the above feature by adding code that will throw an exception in various parts of your application and, as a result get an interactive python prompt at specific parts of your application (for example, before validating your form, or when a method in your model is executed). All this, without the need to use a specific IDE to add breakpoints!

This is an old trick however some people don’t use it and make their work more difficult. Actually, this one of the first things I learned when starting with django and use it all the time since then - I am writing this post mainly to emphasize its usefulness and to urge more people to use it. If you don’t already use it please try it (and thank me later).

Configuration

There are two components you need to install in your django project to use the above technique:

  • django-extensions: a swiss army knife toolset for django - beyond other useful tools it includes a management command (runserver_plus) to start the Werkzeug interactive debugger with your project
  • werkzeug: the werkzeug utility library

Both of these can just be installed with pip (even on windows). After installing them, add django_extensions to your INSTALLED_APPS setting to enable the management command.

After that, you can just run python manage.py runserver_plus - if everything was installed successfully you should see something like this (in windows at least):

(venv) C:\progr\py\werkzeug\testdebug>python manage.py runserver_plus
 * Restarting with stat
Performing system checks...

System check identified no issues (0 silenced).

Django version 1.9.7, using settings 'testdebug.settings'
Development server is running at http://127.0.0.1:8000/
Using the Werkzeug debugger (http://werkzeug.pocoo.org/)
Quit the server with CTRL-BREAK.
 * Debugger is active!
 * Debugger pin code: 143-738-172
 * Debugger is active!
 * Debugger pin code: 174-740-467
 * Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)

Now, the “debugger pin” you see is a way to protect your interactive debugger (i.e it asks for the pin before allowing you to enter the interactive prompt). Since this feature should only be used in your local development system I recommend to just disable it by setting the WERKZEUG_DEBUG_PIN environment variable to off (i.e set WERKZEUG_DEBUG_PIN=off in windows). After that you should see the message “ * Debugger pin disabled. DEBUGGER UNSECURED!“. Please be careful with the interactive debugger and never, ever use it in a production deployment even with the debug pin enabled. I also recommend to use it only on a local development server (i.e the server must be run on 127.0.0.1/local IP and not allow remote connections).

Usage

Now its time for the magic: Let’s add a django view that throws an exception, like this:

def test(request):
    a+=1

to your urls.py ( url(r'^test/', test ) ) and after you visit test you should see something like this:

Werkzeug debugger

Since the a variable was not defined you’ll get an exception when you try to increaseit. Now, notice the console icon in the lower right corner - when you click it you’ll get the interactive debugger! Now you can enter python commands exactly where the a+=1 code was. For example, you can see what are the attributes of the request object you receive (for example, just enter request.GET to output the GET dictionary to the interactive console).

Notice that you can get interactive consoles wherever you want in the stacktrace, i.e I could get a console at line 147 of django.core.handlers.base module on the get_response method — this is needed sometimes especially when you want to see how your code is called by other modules.

Conclusion

As you can see, using the presented technique you can really quickly start an interactive console wherever you want and start entering commands. I use it whenever I need to write anything non trivial (or even trivial stuff - I sometimes prefer opening and interactive debugger to find out by trial and error how should I write a django ORM query than open models.py) and really miss it on other environments (Java).

The above technique should also work with few modifications with other python web frameworks so it’s not django-only.

Finally, please notice that both Werkzeug and django-extensions offer many more tools beyond the interactive debugger presented here - I encourage you to research them since - if you follow my advice - you’ll integrate these to all your django projects!

Understanding nested list comprehension syntax in Python

List comprehensions are one of the really nice and powerful features of Python. It is actually a smart way to introduce new users to functional programming concepts (after all a list comprehension is just a combination of map and filter) and compact statements.

However, one thing that always troubled me when using list comprehensions is their non intuitive syntax when nesting was needed. For example, let’s say that we just want to flatten a list of lists using a nested list comprehension:

non_flat = [ [1,2,3], [4,5,6], [7,8] ]

To write that, somebody would think: For a simple list comprehension I need to write [ x for x in non_flat ] to get all its items - however I want to retrieve each element of the x list so I’ll write something like this:

>>> [y for y in x for x in non_flat]
[7, 7, 7, 8, 8, 8]

Well duh! At this time I’d need research google for a working list comprehension syntax and adjust it to my needs (or give up and write it as a double for loop).

Here’s the correct nested list comprehension people wondering:

>>> [y for x in non_flat for y in x]
[1, 2, 3, 4, 5, 6, 7, 8]

What if I wanted to add a third level of nesting or an if? Well I’d just bite the bullet and use for loops!

However, if you take a look at the document describing list comprehensions in python (PEP 202) you’ll see the following phrase:

It is proposed to allow conditional construction of list literals using for and if clauses. They would nest in the same way for loops and if statements nest now.

This statement explains everything! Just think in for-loops syntax. So, If I used for loops for the previous flattening, I’d do something like:

for x in non_flat:
    for y in x:
        y

which, if y is moved to the front and joined in one line would be the correct nested list comprehension!

So that’s the way… What If I wanted to include only lists with more than 2 elements in the flattening (so [7,8] should not be included)? I’ll write it with for loops first:

for x in non_flat:
    if len(x) > 2
        for y in x:
            y

so by convering this to list comprehension we get:

>>> [ y for x in non_flat if len(x) > 2 for y in x ]
[1, 2, 3, 4, 5, 6]

Success!

One final, more complex example: Let’s say that we have a list of lists of words and we want to get a list of all the letters of these words along with the index of the list they belong to but only for words with more than two characters. Using the same for-loop syntax for the nested list comprehensions we’ll get:

>>> strings = [ ['foo', 'bar'], ['baz', 'taz'], ['w', 'koko'] ]
>>> [ (letter, idx) for idx, lst in enumerate(strings) for word in lst if len(word)>2 for letter in word]
[('f', 0), ('o', 0), ('o', 0), ('b', 0), ('a', 0), ('r', 0), ('b', 1), ('a', 1), ('z', 1), ('t', 1), ('a', 1), ('z', 1), ('k', 2), ('o', 2), ('k', 2), ('o', 2)]