Django Rest Framework: Paginaion в подробном представлении прерывается, когда поле заказа по умолчанию обнуляется - PullRequest
1 голос
/ 22 февраля 2020

У меня есть приложение Django для конкурсов, где конкурс может иметь несколько записей -

Contest в models.py

class Contest(models.Model):
    is_winners_announced = models.BooleanField(default=False)
    ...

ContestEntry в models.py

class ContestEntry(models.Model):
    contest = models.ForeignKey(Contest, on_delete=models.CASCADE,
                                related_name='entries')
    submitted_at = models.DateTimeField(auto_now_add=True)
    assigned_rank = models.PositiveSmallIntegerField(null=True, blank=True)
    ...

В ContestViewSet у меня есть маршрут detail, который обслуживает все записи для конкурса -

def pagination_type_by_field(PaginationClass, field):
    class CustomPaginationClass(PaginationClass):
        ordering = field
    return CustomPaginationClass

...

@decorators.action(
    detail=True,
    methods=['GET'],
)
def entries(self, request, pk=None):
    contest = self.get_object()
    entries = contest.entries.all()

    # Order by rank only if winners are announced
    ordering_array = ['-submitted_at']
    if contest.is_winners_announced:
        ordering_array.insert(0, 'assigned_rank')
    pagination_obj = pagination_type_by_field(
        pagination.CursorPagination, ordering_array)()
    paginated_data = contest_serializers.ContestEntrySerializer(
        instance=pagination_obj.paginate_queryset(entries, request),
        many=True,
        context={'request': request},
    ).data

    return pagination_obj.get_paginated_response(paginated_data)

Разбиение на страницы отлично работает, когда победители не объявлены для конкурса -

GET http://localhost:8000/contests/<id>/entries/

{
    "next": "http://localhost:8000/contests/<id>/entries/?cursor=cD0yMDIwLTAyLTE3KzIwJTNBNDQlM0EwNy4yMDMyMTUlMkIwMCUzQTAw",
    "previous": null,
    "results": [  // Contains all objects and pagination works
        {...},
        ...
    ]
}

Но когда объявлены победители, разбиение на страницы разбивается:

GET http://localhost:8000/contests/<id>/entries/

{
    "next": "https://localhost:8000/contests/4/entries/?cursor=bz03JnA9Mw%3D%3D",
    "previous": null,
    "results": [  // Contains all objects only for the first page; next page is empty even when there are more entries pending to be displayed
        {...},
        ...
    ]
}

Странная вещь, которую я вижу здесь, это то, что курсор в Второй случай выглядит иначе, чем обычно.

1 Ответ

0 голосов
/ 24 февраля 2020

Наконец-то найдено решение проблемы.

Разбивка на страницы не удалась из-за нулевых значений в самом значимом поле упорядочения (т. Е. assigned_rank).

При переходе на следующую страницу, разбиение курсора пытается вычислить следующие строки из базы данных на основе самого низкого значения с предыдущей страницы -

if self.cursor.reverse != is_reversed:
    kwargs = {order_attr + '__lt': current_position}
else:
    kwargs = {order_attr + '__gt': current_position}

queryset = queryset.filter(**kwargs)

Внутренняя реализация

В связи с этим все строки отфильтрованы.


Чтобы предотвратить это, мы можем установить поддельный ранг, который не будет None и не повлияет на порядок в случае, если фактический ранг равен None. Этот запасной ранг может быть max(all_ranks) + 1 -

from django.db.models.functions import Coalesce
...

@decorators.action(
    detail=True,
    methods=['GET'],
)
def entries(self, request, pk=None):
    contest = self.get_object()
    entries = contest.entries.all()

    ordering = ('-submitted_at',)
    if contest.is_winners_announced:
        max_rank = entries.aggregate(
            Max('assigned_rank')
        )['assigned_rank__max']
        next_rank = max_rank + 1
        entries = entries.annotate(
            pseudo_rank=Coalesce('assigned_rank', next_rank))
        ordering = ('pseudo_rank',) + ordering
    ...  # same as before

Подробнее о Coalesce.

Это приведет к установке ранга для всех записей еще на 1 чем запись с худшим рейтингом. Например, если нам присвоены ранги 1, 2 и 3, pseudo_rank для всех остальных записей будет равно 4, и они будут упорядочены по -submitted_at.

И затем, как говорится, «Это сработало». как очарование ✨ ".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...