mirror of
https://github.com/jointakahe/takahe.git
synced 2024-11-22 07:10:59 +00:00
Fix tests and most of pagination
This commit is contained in:
parent
91116fe6f8
commit
fb881dd5de
7 changed files with 65 additions and 60 deletions
|
@ -117,36 +117,17 @@ class PaginationResult:
|
|||
class MastodonPaginator:
|
||||
"""
|
||||
Paginates in the Mastodon style (max_id, min_id, etc).
|
||||
Note that this basically _requires_ us to always do it on IDs, so we do.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
anchor_model: type[models.Model],
|
||||
sort_attribute: str = "created",
|
||||
default_limit: int = 20,
|
||||
max_limit: int = 40,
|
||||
):
|
||||
self.anchor_model = anchor_model
|
||||
self.sort_attribute = sort_attribute
|
||||
self.default_limit = default_limit
|
||||
self.max_limit = max_limit
|
||||
|
||||
def get_anchor(self, anchor_id: str):
|
||||
"""
|
||||
Gets an anchor object by ID.
|
||||
It's possible that the anchor object might be an interaction, in which
|
||||
case we recurse down to its post.
|
||||
"""
|
||||
if anchor_id.startswith("interaction-"):
|
||||
try:
|
||||
return PostInteraction.objects.get(pk=anchor_id[12:])
|
||||
except PostInteraction.DoesNotExist:
|
||||
return None
|
||||
try:
|
||||
return self.anchor_model.objects.get(pk=anchor_id)
|
||||
except self.anchor_model.DoesNotExist:
|
||||
return None
|
||||
|
||||
def paginate(
|
||||
self,
|
||||
queryset,
|
||||
|
@ -156,32 +137,57 @@ class MastodonPaginator:
|
|||
limit: int | None,
|
||||
) -> PaginationResult:
|
||||
if max_id:
|
||||
anchor = self.get_anchor(max_id)
|
||||
if anchor is None:
|
||||
return PaginationResult.empty()
|
||||
queryset = queryset.filter(
|
||||
**{self.sort_attribute + "__lt": getattr(anchor, self.sort_attribute)}
|
||||
)
|
||||
queryset = queryset.filter(id__lt=max_id)
|
||||
|
||||
if since_id:
|
||||
anchor = self.get_anchor(since_id)
|
||||
if anchor is None:
|
||||
return PaginationResult.empty()
|
||||
queryset = queryset.filter(
|
||||
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
|
||||
)
|
||||
queryset = queryset.filter(id__gt=since_id)
|
||||
|
||||
if min_id:
|
||||
# Min ID requires items _immediately_ newer than specified, so we
|
||||
# invert the ordering to accommodate
|
||||
anchor = self.get_anchor(min_id)
|
||||
if anchor is None:
|
||||
return PaginationResult.empty()
|
||||
queryset = queryset.filter(
|
||||
**{self.sort_attribute + "__gt": getattr(anchor, self.sort_attribute)}
|
||||
).order_by(self.sort_attribute)
|
||||
queryset = queryset.filter(id__gt=min_id).order_by("id")
|
||||
else:
|
||||
queryset = queryset.order_by("-" + self.sort_attribute)
|
||||
queryset = queryset.order_by("-id")
|
||||
|
||||
limit = min(limit or self.default_limit, self.max_limit)
|
||||
return PaginationResult(
|
||||
results=list(queryset[:limit]),
|
||||
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.
|
||||
"""
|
||||
if max_id:
|
||||
queryset = queryset.filter(
|
||||
models.Q(subject_post_id__lt=max_id)
|
||||
| models.Q(subject_post_interaction_id__lt=max_id)
|
||||
)
|
||||
|
||||
if since_id:
|
||||
queryset = queryset.filter(
|
||||
models.Q(subject_post_id__gt=max_id)
|
||||
| models.Q(subject_post_interaction_id__gt=max_id)
|
||||
)
|
||||
|
||||
if min_id:
|
||||
# Min ID requires items _immediately_ newer than specified, so we
|
||||
# invert the ordering to accommodate
|
||||
queryset = queryset.filter(
|
||||
models.Q(subject_post_id__gt=max_id)
|
||||
| models.Q(subject_post_interaction_id__gt=max_id)
|
||||
).order_by("id")
|
||||
else:
|
||||
queryset = queryset.order_by("-id")
|
||||
|
||||
limit = min(limit or self.default_limit, self.max_limit)
|
||||
return PaginationResult(
|
||||
|
|
|
@ -3,7 +3,6 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
|
|||
from django.shortcuts import get_object_or_404
|
||||
from ninja import Field
|
||||
|
||||
from activities.models import Post
|
||||
from activities.services import SearchService
|
||||
from api import schemas
|
||||
from api.decorators import identity_required
|
||||
|
@ -151,7 +150,7 @@ def account_statuses(
|
|||
if tagged:
|
||||
queryset = queryset.tagged_with(tagged)
|
||||
# Get user posts with pagination
|
||||
paginator = MastodonPaginator(Post, sort_attribute="published")
|
||||
paginator = MastodonPaginator()
|
||||
pager = paginator.paginate(
|
||||
queryset,
|
||||
min_id=min_id,
|
||||
|
@ -219,7 +218,7 @@ def account_following(
|
|||
|
||||
service = IdentityService(identity)
|
||||
|
||||
paginator = MastodonPaginator(Identity, max_limit=80, sort_attribute="username")
|
||||
paginator = MastodonPaginator(max_limit=80)
|
||||
pager = paginator.paginate(
|
||||
service.following(),
|
||||
min_id=min_id,
|
||||
|
|
|
@ -35,7 +35,7 @@ def notifications(
|
|||
queryset = TimelineService(request.identity).notifications(
|
||||
[base_types[r] for r in requested_types if r in base_types]
|
||||
)
|
||||
paginator = MastodonPaginator(TimelineEvent)
|
||||
paginator = MastodonPaginator()
|
||||
pager = paginator.paginate(
|
||||
queryset,
|
||||
min_id=min_id,
|
||||
|
|
|
@ -16,7 +16,6 @@ from activities.services import PostService
|
|||
from api import schemas
|
||||
from api.views.base import api_router
|
||||
from core.models import Config
|
||||
from users.models import Identity
|
||||
|
||||
from ..decorators import identity_required
|
||||
from ..pagination import MastodonPaginator
|
||||
|
@ -142,20 +141,18 @@ def favourited_by(
|
|||
# a concept of "private status" yet.
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
|
||||
paginator = MastodonPaginator(Identity, sort_attribute="published")
|
||||
paginator = MastodonPaginator()
|
||||
pager = paginator.paginate(
|
||||
post.interactions.filter(
|
||||
type=PostInteraction.Types.like,
|
||||
state__in=PostInteractionStates.group_active(),
|
||||
)
|
||||
.select_related("identity")
|
||||
.order_by("published"),
|
||||
).select_related("identity"),
|
||||
min_id=min_id,
|
||||
max_id=max_id,
|
||||
since_id=since_id,
|
||||
limit=limit,
|
||||
)
|
||||
pager.jsonify_identities()
|
||||
pager.jsonify_results(lambda r: r.identity.to_mastodon_json(include_counts=False))
|
||||
|
||||
if pager.results:
|
||||
response.headers["Link"] = pager.link_header(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
|
||||
from activities.models import Post
|
||||
from activities.services import TimelineService
|
||||
from api import schemas
|
||||
from api.decorators import identity_required
|
||||
|
@ -20,9 +19,9 @@ def home(
|
|||
limit: int = 20,
|
||||
):
|
||||
# Grab a paginated result set of instances
|
||||
paginator = MastodonPaginator(Post, sort_attribute="published")
|
||||
paginator = MastodonPaginator()
|
||||
queryset = TimelineService(request.identity).home()
|
||||
pager = paginator.paginate(
|
||||
pager = paginator.paginate_home(
|
||||
queryset,
|
||||
min_id=min_id,
|
||||
max_id=max_id,
|
||||
|
@ -61,7 +60,7 @@ def public(
|
|||
if only_media:
|
||||
queryset = queryset.filter(attachments__id__isnull=True)
|
||||
# Grab a paginated result set of instances
|
||||
paginator = MastodonPaginator(Post, sort_attribute="published")
|
||||
paginator = MastodonPaginator()
|
||||
pager = paginator.paginate(
|
||||
queryset,
|
||||
min_id=min_id,
|
||||
|
@ -101,7 +100,7 @@ def hashtag(
|
|||
if only_media:
|
||||
queryset = queryset.filter(attachments__id__isnull=True)
|
||||
# Grab a paginated result set of instances
|
||||
paginator = MastodonPaginator(Post, sort_attribute="published")
|
||||
paginator = MastodonPaginator()
|
||||
pager = paginator.paginate(
|
||||
queryset,
|
||||
min_id=min_id,
|
||||
|
|
|
@ -38,15 +38,18 @@ def test_webfinger_system_actor(client):
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_actor(client, identity):
|
||||
def test_delete_unknown_actor(client, identity):
|
||||
"""
|
||||
Tests that unknown actor delete messages are dropped
|
||||
"""
|
||||
data = {
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"actor": "https://mastodon.social/users/fakec8b6984105c8f15070a2",
|
||||
"id": "https://mastodon.social/users/fakec8b6984105c8f15070a2#delete",
|
||||
"object": "https://mastodon.social/users/fakec8b6984105c8f15070a2",
|
||||
"actor": "https://mastodon.test/users/fakec8b6984105c8f15070a2",
|
||||
"id": "https://mastodon.test/users/fakec8b6984105c8f15070a2#delete",
|
||||
"object": "https://mastodon.test/users/fakec8b6984105c8f15070a2",
|
||||
"signature": {
|
||||
"created": "2022-12-06T03:54:28Z",
|
||||
"creator": "https://mastodon.social/users/fakec8b6984105c8f15070a2#main-key",
|
||||
"creator": "https://mastodon.test/users/fakec8b6984105c8f15070a2#main-key",
|
||||
"signatureValue": "This value doesn't matter",
|
||||
"type": "RsaSignature2017",
|
||||
},
|
||||
|
@ -56,4 +59,5 @@ def test_delete_actor(client, identity):
|
|||
resp = client.post(
|
||||
identity.inbox_uri, data=data, content_type="application/activity+json"
|
||||
)
|
||||
print(resp.content)
|
||||
assert resp.status_code == 202
|
||||
|
|
|
@ -132,7 +132,7 @@ class Inbox(View):
|
|||
if (
|
||||
document["type"] == "Delete"
|
||||
and document["actor"] == document["object"]
|
||||
and not identity.pk
|
||||
and identity._state.adding
|
||||
):
|
||||
# We don't have an Identity record for the user. No-op
|
||||
exceptions.capture_message(
|
||||
|
|
Loading…
Reference in a new issue