What we can do is use get a queryset for each of the models we want to display(O(k) queries), sorted on the correct key, and then use a simple merge to combine all of these querysets into a single list, comparing on a given key. While this technically may do more operations than the other methods, it does fewer database queries, and this is often the most difficult portion of your application to scale.
Let's say we have 3 models, new tickets, changesets, and wikipage edits(what you see in a typical Trac install). We can get our querysets and then merge them like so:
def my_view(request):
tickets = Ticket.objects.order_by('create_date')
wikis = WikiEdit.objects.order_by('create_date')
changesets = Changeset.objects.order_by('create_date')
objs = merge(tickets, wikis, changesets, field='create_date')
return render_to_response('my_app/template.html', {'objects': objs})
Now we just need to write our merge function:
def merge_lists(left, right, field=None):
i, j = 0, 0
result = []
while (i < len(left) and j < len(right)):
if getattr(left[i], field) <= getattr(right[j], field):
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
def merge(*querysets, **kwargs):
field = kwargs.pop('field')
if field is None:
raise TypeError('you need to provide a key to do comparisons on')
if len(querysets) == 1:
return querysets[0]
qs = [list(x) for x in querysets]
q1, q2 = qs.pop(), qs.pop()
result = merge_lists(q1, q2, field)
for q in qs:
result = merge_lists(result, q)
return result
There might be a more efficient way to write our merge function, but for now it merges together an arbitrary number of querysets on a given key.
And that's all their is too it. If you see a good way to make the merge function more efficient let me know, I would have liked to use Python's included heapq module, but it doesn't have a way to use a custom comparison function that I saw.