From 20239b5cb7455d593680b17d2d80d2a4850c524d Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sun, 11 Dec 2022 12:37:28 -0700 Subject: Basic post mutation --- activities/models/post.py | 79 +++++++++++++++++++++++++++++++++++-- activities/models/timeline_event.py | 14 +++++-- activities/views/posts.py | 32 +++------------ 3 files changed, 91 insertions(+), 34 deletions(-) (limited to 'activities') diff --git a/activities/models/post.py b/activities/models/post.py index 16e798c..8c2ce13 100644 --- a/activities/models/post.py +++ b/activities/models/post.py @@ -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 diff --git a/activities/models/timeline_event.py b/activities/models/timeline_event.py index 30d473d..16f8632 100644 --- a/activities/models/timeline_event.py +++ b/activities/models/timeline_event.py @@ -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: diff --git a/activities/views/posts.py b/activities/views/posts.py index ccc38fc..e285c7e 100644 --- a/activities/views/posts.py +++ b/activities/views/posts.py @@ -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( -- cgit v1.2.3