[Django]-Getting next and previous objects in Django

59๐Ÿ‘

โœ…

Add an order_by clause to ensure it orders by number.

next_issue = Issue.objects.filter(title=title, number__gt=issue.number).order_by('number').first()
๐Ÿ‘คSteve Mayne

36๐Ÿ‘

I know this is a bit late, but for anyone else, django does have a nicer way to do this, see https://docs.djangoproject.com/en/1.7/ref/models/instances/#django.db.models.Model.get_previous_by_FOO

So the answer here would be something something like

next_issue = Issue.get_next_by_number(issue, title=title)

Django managers to do that with a bit of meta class cleaverness.

๐Ÿ‘คMichael Buckley

8๐Ÿ‘

If itโ€™s required to find next and previous objects ordered by field values that can be equal and those fields are not of Date* type, the query gets slightly complex, because:

  • ordering on objects with same values limiting by [:1] will always produce same result for several objects;
  • object can itself be included in resulting set.

Hereโ€™s are querysets that also take into account the primary keys to produce a correct result (assuming that number parameter from OP is not unique and omitting the title parameter as itโ€™s irrelevant for the example):

Previous:

prev_issue = (Issue.objects
    .filter(number__lte=issue.number, id__lt=instance.id)
    .exclude(id=issue.id)
    .order_by('-number', '-id')
    .first())

Next:

next_issue = (Issue.objects
    .filter(number__gte=issue.number, id__gt=instance.id)
    .exclude(id=issue.id)
    .order_by('number', 'id')
    .first())
๐Ÿ‘คDamaged Organic

0๐Ÿ‘

from functools import partial, reduce
from django.db import models


def next_or_prev_instance(instance, qs=None, prev=False, loop=False):

    if not qs:
        qs = instance.__class__.objects.all()

    if prev:
        qs = qs.reverse()
        lookup = 'lt'
    else:
        lookup = 'gt'

    q_list = []
    prev_fields = []

    if qs.query.extra_order_by:
        ordering = qs.query.extra_order_by
    elif qs.query.order_by:
        ordering = qs.query.order_by
    elif qs.query.get_meta().ordering:
        ordering = qs.query.get_meta().ordering
    else:
        ordering = []

    ordering = list(ordering)

    if 'pk' not in ordering and '-pk' not in ordering:
        ordering.append('pk')
        qs = qs.order_by(*ordering)

    for field in ordering:
        if field[0] == '-':
            this_lookup = (lookup == 'gt' and 'lt' or 'gt')
            field = field[1:]
        else:
            this_lookup = lookup
        q_kwargs = dict([(f, get_model_attr(instance, f))
                         for f in prev_fields])
        key = "%s__%s" % (field, this_lookup)
        q_kwargs[key] = get_model_attr(instance, field)
        q_list.append(models.Q(**q_kwargs))
        prev_fields.append(field)
    try:
        return qs.filter(reduce(models.Q.__or__, q_list))[0]
    except IndexError:
        length = qs.count()
        if loop and length > 1:
            return qs[0]
    return None


next_instance = partial(next_or_prev_instance, prev=False)
prev_instance = partial(next_or_prev_instance, prev=True)
๐Ÿ‘คNarnik Gamarnik

0๐Ÿ‘

note that do not use object.get(pk=object.pk + 1) these sorts of things, IntegrityError occurs if object at that pk is deleted, hence always use a query set

for visitors:

''' Useage '''
"""
    # Declare our item
    store = Store.objects.get(pk=pk)
    # Define our models
    stores = Store.objects.all()
    # Ask for the next item
    new_store = get_next_or_prev(stores, store, 'next')
    # If there is a next item
    if new_store:
        # Replace our item with the next one
        store = new_store
"""

''' Function '''
def get_next_or_prev(models, item, direction):
    '''
    Returns the next or previous item of
    a query-set for 'item'.

    'models' is a query-set containing all
    items of which 'item' is a part of.

    direction is 'next' or 'prev'
    
    '''

    getit = False
    if direction == 'prev':
        models = models.reverse()
    for m in models:
        if getit:
            return m
        if item == m:
            getit = True
    if getit:
        # This would happen when the last
        # item made getit True
        return models[0]
    return False

original author

Usage

# you MUST call order by to pass in an order, otherwise QuerySet.reverse will not work
qs = Model.objects.all().order_by('pk')
q = qs[0]
prev = get_next_or_prev(qs, q, 'prev')
next = get_next_or_prev(qs, q, 'next')
๐Ÿ‘คWeilory

0๐Ÿ‘

It works for me

next_post = Blog.objects.filter(date__lte=self.object.date, published=True).exclude(id=self.object.id).order_by('-date').first()
next_post = next_post if next_post else Blog.objects.filter(published=True).first()
previous_post = Blog.objects.filter(date__gte=self.object.date, published=True).exclude(id=self.object.id).order_by('date').first()
previous_post = previous_post if previous_post else Blog.objects.filter(published=True).last()

-3๐Ÿ‘

next_obj_id = int(current_obj_id) + 1
next_obj = Model.objects.filter(id=next_obj_id).first()

prev_obj_id= int(current_obj_id) โ€“ 1
prev_obj = Model.objects.filter(id=prev_obj_id).first()

#You have nothing to loose hereโ€ฆ This works for me

๐Ÿ‘คOpe

Leave a comment