Фреймворк Django Rest предлагает разбивку на страницы с несколькими полями упорядочения и фильтрами
У меня проблема с DRF, разбиением на страницы и фильтрами.
У меня есть конечная точка. Когда я захожу на начальную страницу конечной точки, я получаю следующий URL-адрес
"next": "http://my-url/api/my-endpoint/?cursor=bz0yMDA%3D&date__gte=2025-04-25T10%3A00%3A00Z&date__lte=2025-04-26T10%3A00%3A00Z"
Когда я обращаюсь к этому URL-адресу, я получаю предыдущий URL-адрес
"next": "http://my-url/api/my-endpoint/?cursor=bz00MDA%3D&date__gte=2025-04-25T10%3A00%3A00Z&date__lte=2025-04-26T10%3A00%3A00Z",
"previous": "http://my-url/api/my-endpoint/?cursor=cj0xJnA9MjAyNS0wNC0yNSsxMCUzQTAwJTNBMDAlMkIwMCUzQTAw&date__gte=2025-04-25T10%3A00%3A00Z&date__lte=2025-04-26T10%3A00%3A00Z",
Теперь, когда я пытаюсь получить доступ к предыдущему URL-адресу, я получаю пустой список результатов.
Вот код для конечной точки
class RevenuePagination(CursorPagination):
page_size = 200
ordering = ['date', 'custom_channel_name', 'date', 'country_name', 'platform_type_code', 'id']
class RevenueFilter(django_filters.FilterSet):
class Meta:
model = Revenue
fields = {
'date': ['lte', 'gte'],
'custom_channel_name': ['exact'],
'country_name': ['exact'],
'platform_type_code': ['exact'],
}
class RevenueViewSet(viewsets.ModelViewSet):
permission_classes = [HasAPIKey]
queryset = Revenue.objects.all()
serializer_class = RevenueSerializer
filterset_class = RevenueFilter
pagination_class = RevenuePagination
ordering = ['date', 'custom_channel_name', 'date', 'country_name', 'platform_type_code', 'id']
ordering_fields = ['date', 'custom_channel_name', 'date', 'country_name', 'platform_type_code', 'id']
@revenue_list_schema()
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
Из того, что я получаю, проблема, похоже, заключается в том, что предыдущий URL-курсор (cj0xJnA9MjAyNS0wNC0yNSsxMCUzQTAwJTNBMDAlMkIwMCUzQTAw) декодируется в r=1&p=2025-04-25+10:00:00+00:00, что неверно, потому что в курсоре есть только поле даты, в то время как у меня есть указано 6 полей в порядке разбивки на страницы.
Я попробовал сузить порядок до ['дата', 'идентификатор'], но это не сработало.
Все поля в порядке являются фактическими полями базы данных (дата-время, символ или целое число), без свойств.
Я действительно испытываю трудности, пробовал отлаживать с помощью Google AI и ChatGPT, но оба они зашли в тупик.
ordering может (и часто должен!) содержать несколько полей.
Но вы правы в том, что в маркере курсора сохраняется только первое поле. Однако остальные поля по-прежнему применяются:
# rest_framework/pagination.py
class CursorPagination(BasePagination):
[...]
def paginate_queryset(self, queryset, request, view=None):
[...]
'''
The below part is where the ordering fields come into play.
They are not stored in the token, instead they are reapplied
on every hit of the paginator.
'''
# Cursor pagination always enforces an ordering.
if reverse:
queryset = queryset.order_by(*_reverse_ordering(self.ordering))
else:
queryset = queryset.order_by(*self.ordering)
Таким образом, эта часть расследования - отвлекающий маневр.
На что я готов поспорить, так это на то, что в вашем списке ordering дважды содержится date. Я не тестировал это, но есть вероятность, что включение одного и того же поля дважды приводит к тому, что в какой-то момент этой операции генерируется какой-то бессмысленный SQL-код. Насколько я знаю, это также может зависеть от данных.
Попробуйте удалить дубликат date.
В качестве альтернативы, проверьте, что выдает .order_by(), и/или просмотрите исходный SQL-код, чтобы убедиться, что запрос не искажен.
Мне удалось решить эту проблему, используя другой модуль `django-cursor-pagination` и обернув paginator из этого модуля в класс разбивки на страницы, как того требует DRF:
https://pypi.org/project/django-cursor-pagination/
А затем использовать этот класс в качестве основы для моего класса RevenuePagination:
class RevenuePagination(CustomCursorPagination):
page_size = 500
max_page_size = 500
ordering = ['date', 'country_name', 'platform_type_code', 'custom_channel_name', 'id']