Skip to content

Instantly share code, notes, and snippets.

@gregmuellegger
Created April 15, 2014 21:50
Show Gist options
  • Save gregmuellegger/10780052 to your computer and use it in GitHub Desktop.
Save gregmuellegger/10780052 to your computer and use it in GitHub Desktop.
Easy to use mixin to get a paginated detail page using django's generic views.
from django.db.models import Q
from django.db.models.sql.query import get_order_dir
# Taken from: https://gist.github.com/dokterbob/1004216
def get_next_or_previous(qs, item, next=True):
"""
Get the next or previous object in the queryset, with regards to the
item specified.
"""
# If we want the previous object, reverse the default ordering
if next:
default_ordering = 'ASC'
else:
default_ordering = 'DESC'
# First, determine the ordering. This code is from get_ordering() in
# django.db.sql.compiler
if qs.query.extra_order_by:
ordering = qs.query.extra_order_by
elif not qs.query.default_ordering:
ordering = qs.query.order_by
else:
ordering = qs.query.order_by or qs.query.model._meta.ordering
assert not ordering == '?', 'This makes no sense for random ordering.'
query_filter = None
for field in ordering:
# Account for possible reverse ordering
field, direction = get_order_dir(field, default_ordering)
item_value = getattr(item, field)
# Either make sure we filter increased values or lesser values
# depending on the sort order
if direction == 'ASC':
filter_dict = {'%s__gt' % field: item_value}
else:
filter_dict = {'%s__lt' % field: item_value}
# Make sure we nicely or the conditions for the queryset
if query_filter:
query_filter = query_filter | Q(**filter_dict)
else:
query_filter = Q(**filter_dict)
# Reverse the order if we're looking for previous items
if default_ordering == 'DESC':
qs = qs.reverse()
# Filter the queryset
qs = qs.filter(query_filter)
# Return either the next/previous item or None if not existent
try:
return qs[0]
except IndexError:
return None
class GetNextAndPreviousMixin(object):
'''
Mix this into a ``django.views.generic.DetailView`` and get a
``{{ previous_object }}`` and ``{{ next_object }}`` based on the currently
used queryset. So you want probably want to use the same queryset ordering
as in the list view.
'''
def get_context_data(self, **kwargs):
queryset = self.get_queryset()
kwargs['previous_object'] = get_next_or_previous(
queryset,
self.object,
next=False)
kwargs['next_object'] = get_next_or_previous(
queryset,
self.object,
next=True)
context_object_name = self.get_context_object_name(self.object)
previous_name = 'previous_{}'.format(context_object_name)
next_name = 'next_{}'.format(context_object_name)
kwargs[previous_name] = kwargs['previous_object']
kwargs[next_name] = kwargs['next_object']
return super(GetNextAndPreviousMixin, self).get_context_data(**kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment