1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
# -*- coding: utf-8 -*-
# from https://github.com/gregplaysguitar/django-next-prev/blob/master/next_prev.py
from functools import partial
from django.db import models
if not locals().get('reduce'):
from functools import reduce
__version__ = '1.0.1'
VERSION = tuple(map(int, __version__.split('.')))
def get_model_attr(instance, attr):
"""Example usage: get_model_attr(instance, 'category__slug')"""
for field in attr.split('__'):
instance = getattr(instance, field)
return instance
def next_or_prev_in_order(instance, qs=None, prev=False, loop=False):
"""Get the next (or previous with prev=True) item for instance, from the
given queryset (which is assumed to contain instance) respecting
queryset ordering. If loop is True, return the first/last item when the
end/start is reached. """
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 the ordering doesn't contain pk, append it and reorder the queryset
# to ensure consistency
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:
# queryset is reversed above if prev
return qs[0]
return None
next_in_order = partial(next_or_prev_in_order, prev=False)
prev_in_order = partial(next_or_prev_in_order, prev=True)
|