Fix page ordering (#535)
This commit is contained in:
parent
6e8149675c
commit
61830a9a9c
|
@ -216,57 +216,45 @@ class MastodonPaginator:
|
||||||
max_id: str | None,
|
max_id: str | None,
|
||||||
since_id: str | None,
|
since_id: str | None,
|
||||||
limit: int | None,
|
limit: int | None,
|
||||||
|
home: bool = False,
|
||||||
) -> PaginationResult[TM]:
|
) -> PaginationResult[TM]:
|
||||||
|
limit = min(limit or self.default_limit, self.max_limit)
|
||||||
|
filters = {}
|
||||||
|
id_field = "id"
|
||||||
|
reverse = False
|
||||||
|
if home:
|
||||||
|
# The home timeline interleaves Post IDs and PostInteraction IDs in an
|
||||||
|
# annotated field called "subject_id".
|
||||||
|
id_field = "subject_id"
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
subject_id=Case(
|
||||||
|
When(type=TimelineEvent.Types.post, then=F("subject_post_id")),
|
||||||
|
default=F("subject_post_interaction"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# These "does not start with interaction" checks can be removed after a
|
# These "does not start with interaction" checks can be removed after a
|
||||||
# couple months, when clients have flushed them out.
|
# couple months, when clients have flushed them out.
|
||||||
if max_id and not max_id.startswith("interaction"):
|
if max_id and not max_id.startswith("interaction"):
|
||||||
queryset = queryset.filter(id__lt=max_id)
|
filters[f"{id_field}__lt"] = max_id
|
||||||
if since_id and not since_id.startswith("interaction"):
|
if since_id and not since_id.startswith("interaction"):
|
||||||
queryset = queryset.filter(id__gt=since_id)
|
filters[f"{id_field}__gt"] = since_id
|
||||||
if min_id and not min_id.startswith("interaction"):
|
if min_id and not min_id.startswith("interaction"):
|
||||||
# Min ID requires items _immediately_ newer than specified, so we
|
# Min ID requires items _immediately_ newer than specified, so we
|
||||||
# invert the ordering to accommodate
|
# invert the ordering to accommodate
|
||||||
queryset = queryset.filter(id__gt=min_id).order_by("id")
|
filters[f"{id_field}__gt"] = min_id
|
||||||
else:
|
reverse = True
|
||||||
queryset = queryset.order_by("-id")
|
|
||||||
|
# Default is to order by ID descending (newest first), except for min_id
|
||||||
|
# queries, which should order by ID for limiting, then reverse the results to be
|
||||||
|
# consistent. The clearest explanation of this I've found so far is this:
|
||||||
|
# https://mastodon.social/@Gargron/100846335353411164
|
||||||
|
ordering = id_field if reverse else f"-{id_field}"
|
||||||
|
results = list(queryset.filter(**filters).order_by(ordering)[:limit])
|
||||||
|
if reverse:
|
||||||
|
results.reverse()
|
||||||
|
|
||||||
limit = min(limit or self.default_limit, self.max_limit)
|
|
||||||
return PaginationResult(
|
return PaginationResult(
|
||||||
results=list(queryset[:limit]),
|
results=results,
|
||||||
limit=limit,
|
|
||||||
)
|
|
||||||
|
|
||||||
def paginate_home(
|
|
||||||
self,
|
|
||||||
queryset,
|
|
||||||
min_id: str | None,
|
|
||||||
max_id: str | None,
|
|
||||||
since_id: str | None,
|
|
||||||
limit: int | None,
|
|
||||||
) -> PaginationResult:
|
|
||||||
"""
|
|
||||||
The home timeline requires special handling where we mix Posts and
|
|
||||||
PostInteractions together.
|
|
||||||
"""
|
|
||||||
queryset = queryset.annotate(
|
|
||||||
event_id=Case(
|
|
||||||
When(type=TimelineEvent.Types.post, then=F("subject_post_id")),
|
|
||||||
default=F("subject_post_interaction"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if max_id and not max_id.startswith("interaction"):
|
|
||||||
queryset = queryset.filter(event_id__lt=max_id)
|
|
||||||
if since_id and not since_id.startswith("interaction"):
|
|
||||||
queryset = queryset.filter(event_id__gt=since_id)
|
|
||||||
if min_id and not min_id.startswith("interaction"):
|
|
||||||
# Min ID requires items _immediately_ newer than specified, so we
|
|
||||||
# invert the ordering to accommodate
|
|
||||||
queryset = queryset.filter(event_id__gt=min_id).order_by("event_id")
|
|
||||||
else:
|
|
||||||
queryset = queryset.order_by("-event_id")
|
|
||||||
|
|
||||||
limit = min(limit or self.default_limit, self.max_limit)
|
|
||||||
return PaginationResult(
|
|
||||||
results=list(queryset[:limit]),
|
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from hatchway import ApiError, ApiResponse, api_view
|
from hatchway import ApiError, ApiResponse, api_view
|
||||||
|
|
||||||
from activities.models import Post
|
from activities.models import Post, TimelineEvent
|
||||||
from activities.services import TimelineService
|
from activities.services import TimelineService
|
||||||
from api import schemas
|
from api import schemas
|
||||||
from api.decorators import scope_required
|
from api.decorators import scope_required
|
||||||
|
@ -34,12 +34,13 @@ def home(
|
||||||
"subject_post_interaction__post__mentions__domain",
|
"subject_post_interaction__post__mentions__domain",
|
||||||
"subject_post_interaction__post__author__posts",
|
"subject_post_interaction__post__author__posts",
|
||||||
)
|
)
|
||||||
pager = paginator.paginate_home(
|
pager: PaginationResult[TimelineEvent] = paginator.paginate(
|
||||||
queryset,
|
queryset,
|
||||||
min_id=min_id,
|
min_id=min_id,
|
||||||
max_id=max_id,
|
max_id=max_id,
|
||||||
since_id=since_id,
|
since_id=since_id,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
|
home=True,
|
||||||
)
|
)
|
||||||
return PaginatingApiResponse(
|
return PaginatingApiResponse(
|
||||||
schemas.Status.map_from_timeline_event(pager.results, request.identity),
|
schemas.Status.map_from_timeline_event(pager.results, request.identity),
|
||||||
|
|
Loading…
Reference in New Issue