Фреймворк 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']
    
Вернуться на верх