Skip to content

Instantly share code, notes, and snippets.

@dokterbob
Created June 2, 2011 10:19
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dokterbob/1004216 to your computer and use it in GitHub Desktop.
Save dokterbob/1004216 to your computer and use it in GitHub Desktop.
Django template tag to efficiently get the next or previous object in the Django queryset, with regards to the item specified.
from django import template
register = template.Library()
from templatetag_sugar.register import tag
from templatetag_sugar.parser import Constant, Variable, Name
from .utils import get_next_or_previous
"""
Efficient and generic get next/previous tags for the Django template language,
using Alex Gaynor's excellent templatetag_sugar library.
The library can be found at: http://pypi.python.org/pypi/django-templatetag-sugar
Usage:
{% load next_previous %}
...
{% get_next in <queryset> after <object> as <next> %}
{% get_previous in <queryset> before <object> as <previous> %}
"""
@tag(register, [Constant("in"), Variable(), Constant("after"), Variable(), Constant("as"), Name()])
def get_next(context, queryset, item, asvar):
context[asvar] = get_next_or_previous(queryset, item, next=True)
return ""
@tag(register, [Constant("in"), Variable(), Constant("before"), Variable(), Constant("as"), Name()])
def get_previous(context, queryset, item, asvar):
context[asvar] = get_next_or_previous(queryset, item, next=False)
return ""
from django.db.models import Q
from django.db.models.sql.query import get_order_dir
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:
item_value = getattr(item, field)
# Account for possible reverse ordering
field, direction = get_order_dir(field, default_ordering)
# 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
@f1nality
Copy link

This will not work correctly for more than 1 order field. You have to split comparison like this:
(1field__gt=val) | (1field=val & 2field__gt=val) ...

@bahiamartins
Copy link

Dude, you saved my life.

@gregplaysguitar
Copy link

gregplaysguitar commented Apr 24, 2017

I wrote a similar tool, partly inspired by this, which isn't tied to the django template engine, so can be used in any context: https://github.com/gregplaysguitar/django-next-prev

@bahiamartins
Copy link

I'm having trouble getting related field using this code. If I have a model:

class UserSkill():
    user = foreignKey(User) 
    ....

so, if I filter: userskill__user__email

I get an error.

How can I solve this?

Thanks

@bahiamartins
Copy link

I moved to https://github.com/gregplaysguitar/django-next-prev a so much better solution since it provides customized order_by

Thanks @gregplaysguitar

@m3ldis
Copy link

m3ldis commented Jan 21, 2021

I couldn't figure this out exactly with my setup, so I wrote a similar one using filter. It's very short because I just needed a previous/next button on an object's detail page. https://gist.github.com/clhefton/a3535e64a9b314339a2e1aabdfb4d1c7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment