Change: Combining Querysets

created on Oct. 31, 2012, 12:21 a.m. by Hevok & updated on Oct. 31, 2012, 12:21 a.m. by Hevok

Different querysets can be combined via this method:

.. sourcecode:: python

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

This results can be further sorted by common attributes:

.. sourcecode:: python

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

Instead of lambda attrgetter can be used:

.. sourcecode:: python

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:

.. sourcecode:: python

matches = current_entries | new_entries | other_entries

An interesting approach is the usage of a QuerySetChain class:

.. sourcecode:: python

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:

.. sourcecode:: python

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.

.. See also Chain multiple querysets into one and Multiple querysets

.. _`Chain multiple querysets into one`: http://djangosnippets.org/snippets/1103/
.. _`Multiple querysets`: http://djangosnippets.org/snippets/1933/
combining-forces.jpg

Categories: Tutorial
Parent: Web Framework

Comment: Created entry.

See entry | Admin

Comment on This Data Unit