Reputation: 18705
I have and object
and QuerySet
which contains this object. I need to get next and previous object
of this QuerySet
.
How can I do that?
I could get next this way:
next = False
for o in QuerySet:
if next:
return o
if o==object:
next = True
but I think it's very slow and unefficient approach on huge QuerySet
s.
Do you know a better solution?
Upvotes: 2
Views: 4150
Reputation: 284
I know this question is a little bit old but I came across this and didn't find very performant solutions, so I hope it may help someone. There are 2 pretty good solutions I came up with.
The 1st is more elegant although slightly worse-performing. The 2nd is significantly faster, especially for larger QuerySets, but it combines using Raw SQL.
They both find previous & next ids, but can, of course, be tweaked to retrieve actual object instances.
1st solution:
object_ids = list(filtered_objects.values_list('id', flat=True))
current_pos = object_ids.index(my_object.id)
if current_pos < len(object_ids) - 1:
next_id = object_ids[current_pos + 1]
if current_pos > 0:
previous_id = object_ids[current_pos - 1]
2nd solution:
window = {'order_by': ordering_fields}
with_neighbor_objects = filtered_objects.annotate(
next_id=Window(
Lead('id'),
**window
),
previous_id=Window(
Lag('id'),
**window
),
)
sql, params = with_neighbor_objects.query.sql_with_params()
# wrap the windowed query with another query using raw SQL, as
# simply using .filter() will destroy the window, as the query itself will change.
current_object_with_neighbors = next(r for r in filtered_objects.raw(f"""
SELECT id, previous_id, next_id FROM ({sql}) filtered_objects_table
WHERE id=%s
""", [*params, object_id]))
next_id = current_object_with_neighbors.next_id:
previous_id = current_object_with_neighbors.previous_id:
Upvotes: 5
Reputation: 3080
Maybe you can use something like that:
def get_prev_next(obj, qset):
assert obj in qset
qset = list(qset)
obj_index = qset.index(obj)
try:
previous = qset[obj_index-1]
except IndexError:
previous = None
try:
next = qset[obj_index+1]
except IndexError:
next = None
return previous,next
It's not very beautiful, but it should work...
Upvotes: 0
Reputation: 9727
Probably that is what you need (in Python 3, if you need a solution for Python 2.7, let me know):
def get_next(queryset, obj):
it = iter(queryset)
while obj is not next(it):
pass
try:
return next(it)
except StopIteraction:
return None
def get_prev(queryset, obj):
prev = None
for o in queryset:
if o is obj:
break
prev = obj
return prev
But there are some notes:
[i + 1]
and [i - 1]
. Otherwise you have to go through whole queryset
to find your object there.QuerySet
, such names should be used for classes. Name it queryset
.Upvotes: 0
Reputation: 914
Using the Django QuerySet API
You can try the following:
For Next:
qs.filter(pk__gt=obj.pk).order_by('pk').first()
For previous:
qs.filter(pk__lt=obj.pk).order_by('-pk').first()
Upvotes: 2