Combining Querysets

Created on Oct. 31, 2012, 12:21 a.m. by Hevok & updated by Hevok on May 2, 2013, 5:24 p.m.

Different querysets can be combined via this method:

from itertools import chain
result_list = list(chain(entry_list, post_list, page_list))

This results can be further sorted by common attributes:

result_list = sorted(
    chain(entry_list, post_list, page_list),
    key=lambda instance: instance.created)

Instead of lambda attrgetter can be used:

result_list = sorted(
    chain(entry_list, post_list, page_list),
    key=attrgetter('created')

If the querysets are from the same model the bitwise or operator comes in handy:

matches = current_entries | new_entries | other_entries

An interesting approach is the usage of a QuerySetChain class:

class QuerySetChain(object):
    """Chains multiple querysets (possibly of different models) and behaves
    as one queryset. Supports minimal methods needed for use with
    django.core.paginator."""

    def __init__(self, *subquerysets):
        self.queryseys = subquerysets

    def count(self):
        """Performs a .count() for all subquerysets and returns the number
        of records as an integer."""
        return sum(qs.count() for qs in self.querysets))

    def _clone(self):
        """Returns a clone of this queryset chain."""
        return self.__class__(*self.querysets)

    def _all(self):
        """Iterates records in all subquerysets."""
        return chain(*self.querysets)

    def __getitem__(self, ndx):
        """Retrieves an item or slice from the chained set of results from
        all subquerysets."""
        if type(ndx) is slice:
            return list(islice(self._all(), ndx.start, ndx.stop, dx.stop or 1))
        else:
            return islice(self.__all(), ndx, ndx+1).next()

This class can be implemented for instance in such a way:

entries = Entry.objects.filter(Q(title__icontains=cleaned_search_term) |
                               Q(text__icontains=cleaned_search_term) |
                               Q(tags__name__icontains=cleanded_search_term))
posts = Post.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(text__icontains=cleaned_search_term) |
                            Q(tags___name__icontains=cleaned_search_term))
pages = Page.objects.filter(Q(title__icontains=cleaned_search_term) |
                            Q(text__icontains=cleaned_search_term) |
                            Q(tags__name__icontains=cleaned_search_term))
matches = QuerySetChain(entries, posts, pages)

matches can then be used with the paginator like it would be used for result_list.

combining-forces.jpg

Tags: query, combine, rest, django, models
Categories: Tutorial
Parent: Web Framework

Update entry (Admin) | See changes

Comment on This Data Unit