Fix mastodon pagination to use returned IDs

This commit is contained in:
Andrew Godwin 2023-01-03 01:01:20 -07:00
parent 68e764a36e
commit 252737f846
5 changed files with 79 additions and 44 deletions

View file

@ -1,5 +1,7 @@
import dataclasses
import urllib.parse
from collections.abc import Callable
from typing import Any
from django.db import models
from django.http import HttpRequest
@ -19,6 +21,9 @@ class PaginationResult:
#: The actual applied limit, which may be different from what was requested.
limit: int
#: A list of transformed JSON objects
json_results: list[dict] | None = None
@classmethod
def empty(cls):
return cls(results=[], limit=20)
@ -29,9 +34,10 @@ class PaginationResult:
"""
if not self.results:
return None
if self.json_results is None:
raise ValueError("You must JSONify the results first")
params = self.filter_params(request, allowed_params)
params["max_id"] = self.results[-1].pk
params["max_id"] = self.json_results[-1]["id"]
return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}"
@ -41,9 +47,10 @@ class PaginationResult:
"""
if not self.results:
return None
if self.json_results is None:
raise ValueError("You must JSONify the results first")
params = self.filter_params(request, allowed_params)
params["min_id"] = self.results[0].pk
params["min_id"] = self.json_results[0]["id"]
return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}"
@ -58,6 +65,45 @@ class PaginationResult:
)
)
def jsonify_results(self, map_function: Callable[[Any], Any]):
"""
Replaces our results with ones transformed via map_function
"""
self.json_results = [map_function(result) for result in self.results]
def jsonify_posts(self, identity):
"""
Predefined way of JSON-ifying Post objects
"""
interactions = PostInteraction.get_post_interactions(self.results, identity)
self.jsonify_results(
lambda post: post.to_mastodon_json(interactions=interactions)
)
def jsonify_status_events(self, identity):
"""
Predefined way of JSON-ifying TimelineEvent objects representing statuses
"""
interactions = PostInteraction.get_event_interactions(self.results, identity)
self.jsonify_results(
lambda event: event.to_mastodon_status_json(interactions=interactions)
)
def jsonify_notification_events(self, identity):
"""
Predefined way of JSON-ifying TimelineEvent objects representing notifications
"""
interactions = PostInteraction.get_event_interactions(self.results, identity)
self.jsonify_results(
lambda event: event.to_mastodon_notification_json(interactions=interactions)
)
def jsonify_identities(self):
"""
Predefined way of JSON-ifying Identity objects
"""
self.jsonify_results(lambda identity: identity.to_mastodon_json())
@staticmethod
def filter_params(request: HttpRequest, allowed_params: list[str]):
params = {}

View file

@ -3,7 +3,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from ninja import Field
from activities.models import Post, PostInteraction
from activities.models import Post
from activities.services import SearchService
from api import schemas
from api.decorators import identity_required
@ -143,7 +143,7 @@ def account_statuses(
queryset = queryset.filter(attachments__pk__isnull=False)
if tagged:
queryset = queryset.tagged_with(tagged)
# Get user posts with pagination
paginator = MastodonPaginator(Post, sort_attribute="published")
pager = paginator.paginate(
queryset,
@ -152,7 +152,9 @@ def account_statuses(
since_id=since_id,
limit=limit,
)
# Convert those to the JSON form
pager.jsonify_posts(identity=request.identity)
# Add a link header if we need to
if pager.results:
response.headers["Link"] = pager.link_header(
request,
@ -166,11 +168,7 @@ def account_statuses(
"tagged",
],
)
interactions = PostInteraction.get_post_interactions(
pager.results, request.identity
)
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
return pager.json_results
@api_router.post("/v1/accounts/{id}/follow", response=schemas.Relationship)
@ -222,6 +220,7 @@ def account_following(
since_id=since_id,
limit=limit,
)
pager.jsonify_identities()
if pager.results:
response.headers["Link"] = pager.link_header(
@ -229,4 +228,4 @@ def account_following(
["limit"],
)
return [result.to_mastodon_json() for result in pager.results]
return pager.json_results

View file

@ -1,6 +1,6 @@
from django.http import HttpRequest, HttpResponse
from activities.models import PostInteraction, TimelineEvent
from activities.models import TimelineEvent
from activities.services import TimelineService
from api import schemas
from api.decorators import identity_required
@ -43,14 +43,9 @@ def notifications(
since_id=since_id,
limit=limit,
)
pager.jsonify_notification_events(identity=request.identity)
if pager.results:
response.headers["Link"] = pager.link_header(request, ["limit", "account_id"])
interactions = PostInteraction.get_event_interactions(
pager.results, request.identity
)
return [
event.to_mastodon_notification_json(interactions=interactions)
for event in pager.results
]
return pager.json_results

View file

@ -155,6 +155,7 @@ def favourited_by(
since_id=since_id,
limit=limit,
)
pager.jsonify_identities()
if pager.results:
response.headers["Link"] = pager.link_header(
@ -162,7 +163,7 @@ def favourited_by(
["limit"],
)
return [result.identity.to_mastodon_json() for result in pager.results]
return pager.json_results
@api_router.post("/v1/statuses/{id}/reblog", response=schemas.Status)

View file

@ -1,6 +1,6 @@
from django.http import HttpRequest, HttpResponse, JsonResponse
from activities.models import Post, PostInteraction
from activities.models import Post
from activities.services import TimelineService
from api import schemas
from api.decorators import identity_required
@ -19,6 +19,7 @@ def home(
min_id: str | None = None,
limit: int = 20,
):
# Grab a paginated result set of instances
paginator = MastodonPaginator(Post, sort_attribute="published")
queryset = TimelineService(request.identity).home()
pager = paginator.paginate(
@ -28,17 +29,12 @@ def home(
since_id=since_id,
limit=limit,
)
interactions = PostInteraction.get_event_interactions(
pager.results, request.identity
)
# Convert those to the JSON form
pager.jsonify_status_events(identity=request.identity)
# Add the link header if needed
if pager.results:
response.headers["Link"] = pager.link_header(request, ["limit"])
return [
event.to_mastodon_status_json(interactions=interactions)
for event in pager.results
]
return pager.json_results
@api_router.get("/v1/timelines/public", response=list[schemas.Status])
@ -64,6 +60,7 @@ def public(
queryset = queryset.filter(local=False)
if only_media:
queryset = queryset.filter(attachments__id__isnull=True)
# Grab a paginated result set of instances
paginator = MastodonPaginator(Post, sort_attribute="published")
pager = paginator.paginate(
queryset,
@ -72,17 +69,15 @@ def public(
since_id=since_id,
limit=limit,
)
# Convert those to the JSON form
pager.jsonify_posts(identity=request.identity)
# Add the link header if needed
if pager.results:
response.headers["Link"] = pager.link_header(
request,
["limit", "local", "remote", "only_media"],
)
interactions = PostInteraction.get_post_interactions(
pager.results, request.identity
)
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
return pager.json_results
@api_router.get("/v1/timelines/tag/{hashtag}", response=list[schemas.Status])
@ -105,6 +100,7 @@ def hashtag(
queryset = queryset.filter(local=True)
if only_media:
queryset = queryset.filter(attachments__id__isnull=True)
# Grab a paginated result set of instances
paginator = MastodonPaginator(Post, sort_attribute="published")
pager = paginator.paginate(
queryset,
@ -113,17 +109,15 @@ def hashtag(
since_id=since_id,
limit=limit,
)
# Convert those to the JSON form
pager.jsonify_posts(identity=request.identity)
# Add a link header if we need to
if pager.results:
response.headers["Link"] = pager.link_header(
request,
["limit", "local", "remote", "only_media"],
)
interactions = PostInteraction.get_post_interactions(
pager.results, request.identity
)
return [post.to_mastodon_json(interactions=interactions) for post in pager.results]
return pager.json_results
@api_router.get("/v1/conversations", response=list[schemas.Status])