Refactor link header and fix empty page case

This commit is contained in:
Andrew Godwin 2022-12-29 11:46:25 -07:00
parent ea6f272047
commit 9c3806a175
4 changed files with 43 additions and 45 deletions

View file

@ -7,11 +7,19 @@ from django.http import HttpRequest
@dataclasses.dataclass @dataclasses.dataclass
class PaginationResult: class PaginationResult:
"""
Represents a pagination result for Mastodon (it does Link header stuff)
"""
#: A list of objects that matched the pagination query. #: A list of objects that matched the pagination query.
results: list[models.Model] results: list[models.Model]
#: The actual applied limit, which may be different from what was requested. #: The actual applied limit, which may be different from what was requested.
limit: int limit: int
sort_attribute: str
@classmethod
def empty(cls):
return cls(results=[], limit=20)
def next(self, request: HttpRequest, allowed_params: list[str]): def next(self, request: HttpRequest, allowed_params: list[str]):
""" """
@ -37,6 +45,17 @@ class PaginationResult:
return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}" return f"{request.build_absolute_uri(request.path)}?{urllib.parse.urlencode(params)}"
def link_header(self, request: HttpRequest, allowed_params: list[str]):
"""
Creates a link header for the given request
"""
return ", ".join(
(
f'<{self.next(request, allowed_params)}>; rel="next"',
f'<{self.prev(request, allowed_params)}>; rel="prev"',
)
)
@staticmethod @staticmethod
def filter_params(request: HttpRequest, allowed_params: list[str]): def filter_params(request: HttpRequest, allowed_params: list[str]):
params = {} params = {}
@ -71,12 +90,12 @@ class MastodonPaginator:
max_id: str | None, max_id: str | None,
since_id: str | None, since_id: str | None,
limit: int | None, limit: int | None,
): ) -> PaginationResult:
if max_id: if max_id:
try: try:
anchor = self.anchor_model.objects.get(pk=max_id) anchor = self.anchor_model.objects.get(pk=max_id)
except self.anchor_model.DoesNotExist: except self.anchor_model.DoesNotExist:
return [] return PaginationResult.empty()
queryset = queryset.filter( queryset = queryset.filter(
**{self.sort_attribute + "__lt": getattr(anchor, self.sort_attribute)} **{self.sort_attribute + "__lt": getattr(anchor, self.sort_attribute)}
) )
@ -85,7 +104,7 @@ class MastodonPaginator:
try: try:
anchor = self.anchor_model.objects.get(pk=since_id) anchor = self.anchor_model.objects.get(pk=since_id)
except self.anchor_model.DoesNotExist: except self.anchor_model.DoesNotExist:
return [] return PaginationResult.empty()
queryset = queryset.filter( queryset = queryset.filter(
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)} **{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
) )
@ -96,7 +115,7 @@ class MastodonPaginator:
try: try:
anchor = self.anchor_model.objects.get(pk=min_id) anchor = self.anchor_model.objects.get(pk=min_id)
except self.anchor_model.DoesNotExist: except self.anchor_model.DoesNotExist:
return [] return PaginationResult.empty()
queryset = queryset.filter( queryset = queryset.filter(
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)} **{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
).order_by(self.sort_attribute) ).order_by(self.sort_attribute)
@ -107,5 +126,4 @@ class MastodonPaginator:
return PaginationResult( return PaginationResult(
results=list(queryset[:limit]), results=list(queryset[:limit]),
limit=limit, limit=limit,
sort_attribute=self.sort_attribute,
) )

View file

@ -132,20 +132,17 @@ def account_statuses(
) )
if pager.results: if pager.results:
params = [ response.headers["Link"] = pager.link_header(
"limit", request,
"id", [
"exclude_reblogs", "limit",
"exclude_replies", "id",
"only_media", "exclude_reblogs",
"pinned", "exclude_replies",
"tagged", "only_media",
] "pinned",
response.headers["Link"] = ", ".join( "tagged",
( ],
f'<{pager.next(request, params)}>; rel="next"',
f'<{pager.prev(request, params)}>; rel="prev"',
)
) )
interactions = PostInteraction.get_post_interactions( interactions = PostInteraction.get_post_interactions(

View file

@ -45,13 +45,7 @@ def notifications(
) )
if pager.results: if pager.results:
params = ["limit", "account_id"] response.headers["Link"] = pager.link_header(request, ["limit", "account_id"])
response.headers["Link"] = ", ".join(
(
f'<{pager.next(request, params)}>; rel="next"',
f'<{pager.prev(request, params)}>; rel="prev"',
)
)
interactions = PostInteraction.get_event_interactions( interactions = PostInteraction.get_event_interactions(
pager.results, request.identity pager.results, request.identity

View file

@ -33,12 +33,7 @@ def home(
) )
if pager.results: if pager.results:
response.headers["Link"] = ", ".join( response.headers["Link"] = pager.link_header(request, ["limit"])
(
f"<{pager.next(request, ['limit'])}>; rel=\"next\"",
f"<{pager.prev(request, ['limit'])}>; rel=\"prev\"",
)
)
return [ return [
event.to_mastodon_status_json(interactions=interactions) event.to_mastodon_status_json(interactions=interactions)
@ -79,12 +74,9 @@ def public(
) )
if pager.results: if pager.results:
params = ["limit", "local", "remote", "only_media"] response.headers["Link"] = pager.link_header(
response.headers["Link"] = ", ".join( request,
( ["limit", "local", "remote", "only_media"],
f'<{pager.next(request, params)}>; rel="next"',
f'<{pager.prev(request, params)}>; rel="prev"',
)
) )
interactions = PostInteraction.get_post_interactions( interactions = PostInteraction.get_post_interactions(
@ -123,12 +115,9 @@ def hashtag(
) )
if pager.results: if pager.results:
params = ["limit", "local", "hashtag", "only_media"] response.headers["Link"] = pager.link_header(
response.headers["Link"] = ", ".join( request,
( ["limit", "local", "remote", "only_media"],
f'<{pager.next(request, params)}>; rel="next"',
f'<{pager.prev(request, params)}>; rel="prev"',
)
) )
interactions = PostInteraction.get_post_interactions( interactions = PostInteraction.get_post_interactions(