Basic post mutation
This commit is contained in:
parent
fc8a21fc5c
commit
20239b5cb7
@ -80,6 +80,12 @@ class PostStates(StateGraph):
|
||||
|
||||
|
||||
class PostQuerySet(models.QuerySet):
|
||||
def not_hidden(self):
|
||||
query = self.exclude(
|
||||
state__in=[PostStates.deleted, PostStates.deleted_fanned_out]
|
||||
)
|
||||
return query
|
||||
|
||||
def public(self, include_replies: bool = False):
|
||||
query = self.filter(
|
||||
visibility__in=[
|
||||
@ -103,6 +109,18 @@ class PostQuerySet(models.QuerySet):
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
def unlisted(self, include_replies: bool = False):
|
||||
query = self.filter(
|
||||
visibility__in=[
|
||||
Post.Visibilities.public,
|
||||
Post.Visibilities.local_only,
|
||||
Post.Visibilities.unlisted,
|
||||
],
|
||||
)
|
||||
if not include_replies:
|
||||
return query.filter(in_reply_to__isnull=True)
|
||||
return query
|
||||
|
||||
def tagged_with(self, hashtag: str | Hashtag):
|
||||
if isinstance(hashtag, str):
|
||||
tag_q = models.Q(hashtags__contains=hashtag)
|
||||
@ -118,12 +136,18 @@ class PostManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return PostQuerySet(self.model, using=self._db)
|
||||
|
||||
def not_hidden(self):
|
||||
return self.get_queryset().not_hidden()
|
||||
|
||||
def public(self, include_replies: bool = False):
|
||||
return self.get_queryset().public(include_replies=include_replies)
|
||||
|
||||
def local_public(self, include_replies: bool = False):
|
||||
return self.get_queryset().local_public(include_replies=include_replies)
|
||||
|
||||
def unlisted(self, include_replies: bool = False):
|
||||
return self.get_queryset().unlisted(include_replies=include_replies)
|
||||
|
||||
def tagged_with(self, hashtag: str | Hashtag):
|
||||
return self.get_queryset().tagged_with(hashtag=hashtag)
|
||||
|
||||
@ -248,6 +272,8 @@ class Post(StatorModel):
|
||||
"""
|
||||
Returns the actual Post object we're replying to, if we can find it
|
||||
"""
|
||||
if self.in_reply_to is None:
|
||||
return None
|
||||
return (
|
||||
Post.objects.filter(object_uri=self.in_reply_to)
|
||||
.select_related("author")
|
||||
@ -338,6 +364,7 @@ class Post(StatorModel):
|
||||
author: Identity,
|
||||
content: str,
|
||||
summary: str | None = None,
|
||||
sensitive: bool = False,
|
||||
visibility: int = Visibilities.public,
|
||||
reply_to: Optional["Post"] = None,
|
||||
attachments: list | None = None,
|
||||
@ -359,7 +386,7 @@ class Post(StatorModel):
|
||||
author=author,
|
||||
content=content,
|
||||
summary=summary or None,
|
||||
sensitive=bool(summary),
|
||||
sensitive=bool(summary) or sensitive,
|
||||
local=True,
|
||||
visibility=visibility,
|
||||
hashtags=hashtags,
|
||||
@ -424,6 +451,48 @@ class Post(StatorModel):
|
||||
hashtag=hashtag,
|
||||
)
|
||||
|
||||
### Actions ###
|
||||
|
||||
def interact_as(self, identity, type):
|
||||
from activities.models import PostInteraction, PostInteractionStates
|
||||
|
||||
interaction = PostInteraction.objects.get_or_create(
|
||||
type=type, identity=identity, post=self
|
||||
)[0]
|
||||
if interaction.state in [
|
||||
PostInteractionStates.undone,
|
||||
PostInteractionStates.undone_fanned_out,
|
||||
]:
|
||||
interaction.transition_perform(PostInteractionStates.new)
|
||||
|
||||
def uninteract_as(self, identity, type):
|
||||
from activities.models import PostInteraction, PostInteractionStates
|
||||
|
||||
for interaction in PostInteraction.objects.filter(
|
||||
type=type, identity=identity, post=self
|
||||
):
|
||||
interaction.transition_perform(PostInteractionStates.undone)
|
||||
|
||||
def like_as(self, identity):
|
||||
from activities.models import PostInteraction
|
||||
|
||||
self.interact_as(identity, PostInteraction.Types.like)
|
||||
|
||||
def unlike_as(self, identity):
|
||||
from activities.models import PostInteraction
|
||||
|
||||
self.uninteract_as(identity, PostInteraction.Types.like)
|
||||
|
||||
def boost_as(self, identity):
|
||||
from activities.models import PostInteraction
|
||||
|
||||
self.interact_as(identity, PostInteraction.Types.boost)
|
||||
|
||||
def unboost_as(self, identity):
|
||||
from activities.models import PostInteraction
|
||||
|
||||
self.uninteract_as(identity, PostInteraction.Types.boost)
|
||||
|
||||
### ActivityPub (outbound) ###
|
||||
|
||||
def to_ap(self) -> dict:
|
||||
@ -711,11 +780,11 @@ class Post(StatorModel):
|
||||
|
||||
### Mastodon API ###
|
||||
|
||||
def to_mastodon_json(self):
|
||||
def to_mastodon_json(self, interactions=None):
|
||||
reply_parent = None
|
||||
if self.in_reply_to:
|
||||
reply_parent = Post.objects.filter(object_uri=self.in_reply_to).first()
|
||||
return {
|
||||
value = {
|
||||
"id": self.pk,
|
||||
"uri": self.object_uri,
|
||||
"created_at": format_ld_date(self.published),
|
||||
@ -755,3 +824,7 @@ class Post(StatorModel):
|
||||
"text": self.safe_content_plain(),
|
||||
"edited_at": format_ld_date(self.edited) if self.edited else None,
|
||||
}
|
||||
if interactions:
|
||||
value["favourited"] = self.pk in interactions.get("like", [])
|
||||
value["reblogged"] = self.pk in interactions.get("boost", [])
|
||||
return value
|
||||
|
@ -148,7 +148,7 @@ class TimelineEvent(models.Model):
|
||||
|
||||
### Mastodon Client API ###
|
||||
|
||||
def to_mastodon_notification_json(self):
|
||||
def to_mastodon_notification_json(self, interactions=None):
|
||||
result = {
|
||||
"id": self.pk,
|
||||
"created_at": format_ld_date(self.created),
|
||||
@ -156,13 +156,19 @@ class TimelineEvent(models.Model):
|
||||
}
|
||||
if self.type == self.Types.liked:
|
||||
result["type"] = "favourite"
|
||||
result["status"] = self.subject_post.to_mastodon_json()
|
||||
result["status"] = self.subject_post.to_mastodon_json(
|
||||
interactions=interactions
|
||||
)
|
||||
elif self.type == self.Types.boosted:
|
||||
result["type"] = "reblog"
|
||||
result["status"] = self.subject_post.to_mastodon_json()
|
||||
result["status"] = self.subject_post.to_mastodon_json(
|
||||
interactions=interactions
|
||||
)
|
||||
elif self.type == self.Types.mentioned:
|
||||
result["type"] = "mention"
|
||||
result["status"] = self.subject_post.to_mastodon_json()
|
||||
result["status"] = self.subject_post.to_mastodon_json(
|
||||
interactions=interactions
|
||||
)
|
||||
elif self.type == self.Types.followed:
|
||||
result["type"] = "follow"
|
||||
else:
|
||||
|
@ -6,7 +6,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from activities.models import Post, PostInteraction, PostInteractionStates, PostStates
|
||||
from activities.models import Post, PostInteraction, PostStates
|
||||
from core.decorators import cache_page_by_ap_json
|
||||
from core.ld import canonicalise
|
||||
from users.decorators import identity_required
|
||||
@ -94,20 +94,9 @@ class Like(View):
|
||||
identity.posts.prefetch_related("attachments"), pk=post_id
|
||||
)
|
||||
if self.undo:
|
||||
# Undo any likes on the post
|
||||
for interaction in PostInteraction.objects.filter(
|
||||
type=PostInteraction.Types.like,
|
||||
identity=request.identity,
|
||||
post=post,
|
||||
):
|
||||
interaction.transition_perform(PostInteractionStates.undone)
|
||||
post.unlike_as(self.request.identity)
|
||||
else:
|
||||
# Make a like on this post if we didn't already
|
||||
PostInteraction.objects.get_or_create(
|
||||
type=PostInteraction.Types.like,
|
||||
identity=request.identity,
|
||||
post=post,
|
||||
)
|
||||
post.like_as(self.request.identity)
|
||||
# Return either a redirect or a HTMX snippet
|
||||
if request.htmx:
|
||||
return render(
|
||||
@ -133,20 +122,9 @@ class Boost(View):
|
||||
identity = by_handle_or_404(self.request, handle, local=False)
|
||||
post = get_object_or_404(identity.posts, pk=post_id)
|
||||
if self.undo:
|
||||
# Undo any boosts on the post
|
||||
for interaction in PostInteraction.objects.filter(
|
||||
type=PostInteraction.Types.boost,
|
||||
identity=request.identity,
|
||||
post=post,
|
||||
):
|
||||
interaction.transition_perform(PostInteractionStates.undone)
|
||||
post.unboost_as(request.identity)
|
||||
else:
|
||||
# Make a boost on this post if we didn't already
|
||||
PostInteraction.objects.get_or_create(
|
||||
type=PostInteraction.Types.boost,
|
||||
identity=request.identity,
|
||||
post=post,
|
||||
)
|
||||
post.boost_as(request.identity)
|
||||
# Return either a redirect or a HTMX snippet
|
||||
if request.htmx:
|
||||
return render(
|
||||
|
@ -160,3 +160,8 @@ class Relationship(Schema):
|
||||
domain_blocking: bool
|
||||
endorsed: bool
|
||||
note: str
|
||||
|
||||
|
||||
class Context(Schema):
|
||||
ancestors: list[Status]
|
||||
descendants: list[Status]
|
||||
|
@ -1,8 +1,10 @@
|
||||
from .accounts import * # noqa
|
||||
from .apps import * # noqa
|
||||
from .base import api_router # noqa
|
||||
from .filters import * # noqa
|
||||
from .instance import * # noqa
|
||||
from .media import * # noqa
|
||||
from .notifications import * # noqa
|
||||
from .oauth import * # noqa
|
||||
from .search import * # noqa
|
||||
from .statuses import * # noqa
|
||||
from .timelines import * # noqa
|
||||
|
@ -1,12 +1,11 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from activities.models import Post
|
||||
from activities.models import Post, PostInteraction
|
||||
from api import schemas
|
||||
from api.decorators import identity_required
|
||||
from api.views.base import api_router
|
||||
from users.models import Identity
|
||||
|
||||
from ..decorators import identity_required
|
||||
|
||||
|
||||
@api_router.get("/v1/accounts/verify_credentials", response=schemas.Account)
|
||||
@identity_required
|
||||
@ -69,7 +68,8 @@ def account_statuses(
|
||||
):
|
||||
identity = get_object_or_404(Identity, pk=id)
|
||||
posts = (
|
||||
identity.posts.public()
|
||||
identity.posts.not_hidden()
|
||||
.unlisted(include_replies=not exclude_replies)
|
||||
.select_related("author")
|
||||
.prefetch_related("attachments")
|
||||
.order_by("-created")
|
||||
@ -91,4 +91,6 @@ def account_statuses(
|
||||
# invert the ordering to accomodate
|
||||
anchor_post = Post.objects.get(pk=min_id)
|
||||
posts = posts.filter(created__gt=anchor_post.created).order_by("created")
|
||||
return [post.to_mastodon_json() for post in posts[:limit]]
|
||||
posts = list(posts[:limit])
|
||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
||||
return [post.to_mastodon_json(interactions=interactions) for post in posts]
|
||||
|
9
api/views/filters.py
Normal file
9
api/views/filters.py
Normal file
@ -0,0 +1,9 @@
|
||||
from api.views.base import api_router
|
||||
|
||||
from ..decorators import identity_required
|
||||
|
||||
|
||||
@api_router.get("/v1/filters")
|
||||
@identity_required
|
||||
def status(request):
|
||||
return []
|
76
api/views/media.py
Normal file
76
api/views/media.py
Normal file
@ -0,0 +1,76 @@
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja import File, Schema
|
||||
from ninja.files import UploadedFile
|
||||
|
||||
from activities.models import PostAttachment, PostAttachmentStates
|
||||
from api import schemas
|
||||
from api.views.base import api_router
|
||||
from core.files import blurhash_image, resize_image
|
||||
|
||||
from ..decorators import identity_required
|
||||
|
||||
|
||||
class UploadMediaSchema(Schema):
|
||||
description: str = ""
|
||||
focus: str = "0,0"
|
||||
|
||||
|
||||
@api_router.post("/v1/media", response=schemas.MediaAttachment)
|
||||
@api_router.post("/v2/media", response=schemas.MediaAttachment)
|
||||
@identity_required
|
||||
def upload_media(
|
||||
request,
|
||||
file: UploadedFile = File(...),
|
||||
details: UploadMediaSchema | None = None,
|
||||
):
|
||||
main_file = resize_image(
|
||||
file,
|
||||
size=(2000, 2000),
|
||||
cover=False,
|
||||
)
|
||||
thumbnail_file = resize_image(
|
||||
file,
|
||||
size=(400, 225),
|
||||
cover=True,
|
||||
)
|
||||
attachment = PostAttachment.objects.create(
|
||||
blurhash=blurhash_image(thumbnail_file),
|
||||
mimetype="image/webp",
|
||||
width=main_file.image.width,
|
||||
height=main_file.image.height,
|
||||
name=details.description if details else None,
|
||||
state=PostAttachmentStates.fetched,
|
||||
)
|
||||
attachment.file.save(
|
||||
main_file.name,
|
||||
main_file,
|
||||
)
|
||||
attachment.thumbnail.save(
|
||||
thumbnail_file.name,
|
||||
thumbnail_file,
|
||||
)
|
||||
attachment.save()
|
||||
return attachment.to_mastodon_json()
|
||||
|
||||
|
||||
@api_router.get("/v1/media/{id}", response=schemas.MediaAttachment)
|
||||
@identity_required
|
||||
def get_media(
|
||||
request,
|
||||
id: str,
|
||||
):
|
||||
attachment = get_object_or_404(PostAttachment, pk=id)
|
||||
return attachment.to_mastodon_json()
|
||||
|
||||
|
||||
@api_router.put("/v1/media/{id}", response=schemas.MediaAttachment)
|
||||
@identity_required
|
||||
def update_media(
|
||||
request,
|
||||
id: str,
|
||||
details: UploadMediaSchema | None = None,
|
||||
):
|
||||
attachment = get_object_or_404(PostAttachment, pk=id)
|
||||
attachment.name = details.description if details else None
|
||||
attachment.save()
|
||||
return attachment.to_mastodon_json()
|
@ -1,8 +1,7 @@
|
||||
from activities.models import TimelineEvent
|
||||
|
||||
from .. import schemas
|
||||
from ..decorators import identity_required
|
||||
from .base import api_router
|
||||
from activities.models import PostInteraction, TimelineEvent
|
||||
from api import schemas
|
||||
from api.decorators import identity_required
|
||||
from api.views.base import api_router
|
||||
|
||||
|
||||
@api_router.get("/v1/notifications", response=list[schemas.Notification])
|
||||
@ -49,4 +48,9 @@ def notifications(
|
||||
# invert the ordering to accomodate
|
||||
anchor_event = TimelineEvent.objects.get(pk=min_id)
|
||||
events = events.filter(created__gt=anchor_event.created).order_by("created")
|
||||
return [event.to_mastodon_notification_json() for event in events[:limit]]
|
||||
events = list(events[:limit])
|
||||
interactions = PostInteraction.get_event_interactions(events, request.identity)
|
||||
return [
|
||||
event.to_mastodon_notification_json(interactions=interactions)
|
||||
for event in events
|
||||
]
|
||||
|
@ -2,6 +2,7 @@ from typing import Literal
|
||||
|
||||
from ninja import Field
|
||||
|
||||
from activities.models import PostInteraction
|
||||
from activities.search import Searcher
|
||||
from api import schemas
|
||||
from api.decorators import identity_required
|
||||
@ -38,5 +39,11 @@ def search(
|
||||
if type is None or type == "hashtag":
|
||||
result["hashtag"] = [h.to_mastodon_json() for h in search_result["hashtags"]]
|
||||
if type is None or type == "statuses":
|
||||
result["statuses"] = [p.to_mastodon_json() for p in search_result["posts"]]
|
||||
interactions = PostInteraction.get_post_interactions(
|
||||
search_result["posts"], request.identity
|
||||
)
|
||||
result["statuses"] = [
|
||||
p.to_mastodon_json(interactions=interactions)
|
||||
for p in search_result["posts"]
|
||||
]
|
||||
return result
|
||||
|
139
api/views/statuses.py
Normal file
139
api/views/statuses.py
Normal file
@ -0,0 +1,139 @@
|
||||
from typing import Literal
|
||||
|
||||
from django.forms import ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from ninja import Schema
|
||||
|
||||
from activities.models import (
|
||||
Post,
|
||||
PostAttachment,
|
||||
PostInteraction,
|
||||
PostStates,
|
||||
TimelineEvent,
|
||||
)
|
||||
from api import schemas
|
||||
from api.views.base import api_router
|
||||
from core.models import Config
|
||||
|
||||
from ..decorators import identity_required
|
||||
|
||||
|
||||
class PostStatusSchema(Schema):
|
||||
status: str
|
||||
in_reply_to_id: str | None = None
|
||||
sensitive: bool = False
|
||||
spoiler_text: str | None = None
|
||||
visibility: Literal["public", "unlisted", "private", "direct"] = "public"
|
||||
language: str | None = None
|
||||
scheduled_at: str | None = None
|
||||
media_ids: list[str] = []
|
||||
|
||||
|
||||
@api_router.post("/v1/statuses", response=schemas.Status)
|
||||
@identity_required
|
||||
def post_status(request, details: PostStatusSchema):
|
||||
# Check text length
|
||||
if len(details.status) > Config.system.post_length:
|
||||
raise ValidationError("Status is too long")
|
||||
if len(details.status) == 0 and not details.media_ids:
|
||||
raise ValidationError("Status is empty")
|
||||
# Grab attachments
|
||||
attachments = [get_object_or_404(PostAttachment, pk=id) for id in details.media_ids]
|
||||
# Create the Post
|
||||
visibility_map = {
|
||||
"public": Post.Visibilities.public,
|
||||
"unlisted": Post.Visibilities.unlisted,
|
||||
"private": Post.Visibilities.followers,
|
||||
"direct": Post.Visibilities.mentioned,
|
||||
}
|
||||
reply_post = None
|
||||
if details.in_reply_to_id:
|
||||
try:
|
||||
reply_post = Post.objects.get(pk=details.in_reply_to_id)
|
||||
except Post.DoesNotExist:
|
||||
pass
|
||||
post = Post.create_local(
|
||||
author=request.identity,
|
||||
content=details.status,
|
||||
summary=details.spoiler_text,
|
||||
sensitive=details.sensitive,
|
||||
visibility=visibility_map[details.visibility],
|
||||
reply_to=reply_post,
|
||||
attachments=attachments,
|
||||
)
|
||||
# Add their own timeline event for immediate visibility
|
||||
TimelineEvent.add_post(request.identity, post)
|
||||
return post.to_mastodon_json()
|
||||
|
||||
|
||||
@api_router.get("/v1/statuses/{id}", response=schemas.Status)
|
||||
@identity_required
|
||||
def status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
interactions = PostInteraction.get_post_interactions([post], request.identity)
|
||||
return post.to_mastodon_json(interactions=interactions)
|
||||
|
||||
|
||||
@api_router.delete("/v1/statuses/{id}", response=schemas.Status)
|
||||
@identity_required
|
||||
def delete_status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
post.transition_perform(PostStates.deleted)
|
||||
TimelineEvent.objects.filter(subject_post=post, identity=request.identity).delete()
|
||||
return post.to_mastodon_json()
|
||||
|
||||
|
||||
@api_router.get("/v1/statuses/{id}/context", response=schemas.Context)
|
||||
@identity_required
|
||||
def status_context(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
parent = post.in_reply_to_post()
|
||||
ancestors = []
|
||||
if parent:
|
||||
ancestors.append(parent)
|
||||
descendants = list(Post.objects.filter(in_reply_to=post.object_uri)[:40])
|
||||
interactions = PostInteraction.get_post_interactions(
|
||||
[post] + ancestors + descendants, request.identity
|
||||
)
|
||||
return {
|
||||
"ancestors": [p.to_mastodon_json(interactions=interactions) for p in ancestors],
|
||||
"descendants": [
|
||||
p.to_mastodon_json(interactions=interactions) for p in descendants
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@api_router.post("/v1/statuses/{id}/favourite", response=schemas.Status)
|
||||
@identity_required
|
||||
def favourite_status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
post.like_as(request.identity)
|
||||
interactions = PostInteraction.get_post_interactions([post], request.identity)
|
||||
return post.to_mastodon_json(interactions=interactions)
|
||||
|
||||
|
||||
@api_router.post("/v1/statuses/{id}/unfavourite", response=schemas.Status)
|
||||
@identity_required
|
||||
def unfavourite_status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
post.unlike_as(request.identity)
|
||||
interactions = PostInteraction.get_post_interactions([post], request.identity)
|
||||
return post.to_mastodon_json(interactions=interactions)
|
||||
|
||||
|
||||
@api_router.post("/v1/statuses/{id}/reblog", response=schemas.Status)
|
||||
@identity_required
|
||||
def reblog_status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
post.boost_as(request.identity)
|
||||
interactions = PostInteraction.get_post_interactions([post], request.identity)
|
||||
return post.to_mastodon_json(interactions=interactions)
|
||||
|
||||
|
||||
@api_router.post("/v1/statuses/{id}/unreblog", response=schemas.Status)
|
||||
@identity_required
|
||||
def unreblog_status(request, id: str):
|
||||
post = get_object_or_404(Post, pk=id)
|
||||
post.unboost_as(request.identity)
|
||||
interactions = PostInteraction.get_post_interactions([post], request.identity)
|
||||
return post.to_mastodon_json(interactions=interactions)
|
@ -1,4 +1,4 @@
|
||||
from activities.models import Post, TimelineEvent
|
||||
from activities.models import Post, PostInteraction, TimelineEvent
|
||||
|
||||
from .. import schemas
|
||||
from ..decorators import identity_required
|
||||
@ -36,7 +36,12 @@ def home(
|
||||
# invert the ordering to accomodate
|
||||
anchor_post = Post.objects.get(pk=min_id)
|
||||
events = events.filter(created__gt=anchor_post.created).order_by("created")
|
||||
return [event.subject_post.to_mastodon_json() for event in events[:limit]]
|
||||
events = list(events[:limit])
|
||||
interactions = PostInteraction.get_event_interactions(events, request.identity)
|
||||
return [
|
||||
event.subject_post.to_mastodon_json(interactions=interactions)
|
||||
for event in events
|
||||
]
|
||||
|
||||
|
||||
@api_router.get("/v1/timelines/public", response=list[schemas.Status])
|
||||
@ -76,7 +81,9 @@ def public(
|
||||
# invert the ordering to accomodate
|
||||
anchor_post = Post.objects.get(pk=min_id)
|
||||
posts = posts.filter(created__gt=anchor_post.created).order_by("created")
|
||||
return [post.to_mastodon_json() for post in posts[:limit]]
|
||||
posts = list(posts[:limit])
|
||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
||||
return [post.to_mastodon_json(interactions=interactions) for post in posts]
|
||||
|
||||
|
||||
@api_router.get("/v1/timelines/tag/{hashtag}", response=list[schemas.Status])
|
||||
@ -115,7 +122,9 @@ def hashtag(
|
||||
# invert the ordering to accomodate
|
||||
anchor_post = Post.objects.get(pk=min_id)
|
||||
posts = posts.filter(created__gt=anchor_post.created).order_by("created")
|
||||
return [post.to_mastodon_json() for post in posts[:limit]]
|
||||
posts = list(posts[:limit])
|
||||
interactions = PostInteraction.get_post_interactions(posts, request.identity)
|
||||
return [post.to_mastodon_json(interactions=interactions) for post in posts]
|
||||
|
||||
|
||||
@api_router.get("/v1/conversations", response=list[schemas.Status])
|
||||
|
@ -32,8 +32,8 @@ def resize_image(
|
||||
return file
|
||||
|
||||
|
||||
def blurhash_image(image) -> str:
|
||||
def blurhash_image(file) -> str:
|
||||
"""
|
||||
Returns the blurhash for an image
|
||||
"""
|
||||
return blurhash.encode(image, 4, 4)
|
||||
return blurhash.encode(file, 4, 4)
|
||||
|
@ -62,9 +62,8 @@ class ViewIdentity(ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
self.identity.posts.filter(
|
||||
visibility__in=[Post.Visibilities.public, Post.Visibilities.unlisted],
|
||||
)
|
||||
self.identity.posts.not_hidden()
|
||||
.unlisted(include_replies=True)
|
||||
.select_related("author")
|
||||
.prefetch_related("attachments")
|
||||
.order_by("-created")
|
||||
|
Reference in New Issue
Block a user