В этом есть две трудности:
- Чтобы собрать тот же набор запросов , который использовался для перечисления объектов, и
- Возможно изменение этого набора запросов для получениятолько объект, следующий за текущим (без необходимости проходить через все это).
Что касается первого: Django сохраняет параметры, использованные для генерации списка, в GETпараметр называется _changelist_filters
, но этот аргумент не используется changeform_view
.Он только «восстанавливается» как параметры GET при возврате в список.Чтобы использовать этот аргумент, мы должны изменить HttpRequest
(что звучит плохо), чтобы мы могли создать экземпляр django.contrib.admin.views.main.ChangeList
- объект, который будет обрабатывать эти фильтры и генерировать набор запросов.
Во-вторых , чтобы найти первичный ключ в середине запроса, который может быть упорядочен любым другим полем, я не нашел другого решения, кроме как выполнить линейный поиск по самому запросу. (Возможно, кто-то может пролить некоторый свет на это.)
В результате получается этот миксин, добавляемый в ModelAdmin
класс:
from django.http import QueryDict
class GotoNextAdminMixin(object):
def get_next_instance_pk(self, request, current):
"""Returns the primary key of the next object in the query (considering filters and ordering).
Returns None if the object is not in the queryset.
"""
querystring = request.GET.get('_changelist_filters')
if querystring:
# Alters the HttpRequest object to make it function as a list request
original_get = request.GET
try:
request.GET = QueryDict(querystring)
# from django.contrib.admin.options: ModelAdmin.changelist_view
ChangeList = self.get_changelist(request)
list_display = self.get_list_display(request)
changelist = ChangeList(
request, self.model, list_display,
self.get_list_display_links(request, list_display),
self.get_list_filter(request),
self.date_hierarchy,
self.get_search_fields(request),
self.get_list_select_related(request),
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self)
queryset = changelist.get_queryset(request)
finally:
request.GET = original_get
else:
queryset = self.get_queryset(request)
# Try to find pk in this list:
iterator = queryset.values_list('pk', flat=True).iterator()
try:
while next(iterator) != current.pk:
continue
return next(iterator)
except StopIteration:
pass # Not found or it was the last item
Затем в моем ModelAdminкласс:
class MyModelAdmin(admin.ModelAdmin, GotoNextAdminMixin):
def response_change(self, request, obj):
"""Determines the HttpResponse for the change_view stage."""
if '_save_next' in request.POST:
next_pk = self.get_next_instance_pk(request, obj)
if next_pk:
response = redirect('admin:app_mymodel_change', next_pk)
qs = request.GET.urlencode() # keeps _changelist_filters
else:
# Last item (or no longer in list) - go back to list in the same position
response = redirect('admin:app_mymodel_changelist')
qs = request.GET.get('_changelist_filters')
if qs:
response['Location'] += '?' + qs
return response
return super().response_change(request, obj)
Недостатки этого подхода:
- Необходимо изменить переменную HttpRequest.GET.В текущей версии Django это не свойство, поэтому оно может изменяться, но в будущем это может быть не так.
- Должен выполнять итерацию по всему набору запросов (хотя мы получаем только PK) - плохо для больших таблиц или дорогозапросы.
- Работает только для моделей с первичным ключом с одним полем (как и большинство вещей в Django, поэтому это не может быть проблемой).
- Если объект больше не находится в списке(например, был фильтр и текущий объект был отфильтрован), усилия были потрачены впустую.
Плюсы: