Move like/boost/reply counts onto Post model
This commit is contained in:
parent
e5ef34a1b9
commit
0fc8ff4965
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.1.4 on 2022-12-31 20:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("activities", "0006_fanout_subject_identity_alter_fanout_type"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="post",
|
||||
name="stats",
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
]
|
|
@ -279,6 +279,9 @@ class Post(StatorModel):
|
|||
blank=True,
|
||||
)
|
||||
|
||||
# Like/Boost/etc counts
|
||||
stats = models.JSONField(blank=True, null=True)
|
||||
|
||||
# When the post was originally created (as opposed to when we received it)
|
||||
published = models.DateTimeField(default=timezone.now)
|
||||
|
||||
|
@ -404,6 +407,17 @@ class Post(StatorModel):
|
|||
return ""
|
||||
return "summary-" + text.slugify(self.summary, allow_unicode=True)
|
||||
|
||||
@property
|
||||
def stats_with_defaults(self):
|
||||
"""
|
||||
Returns the stats dict with counts of likes/etc. in it
|
||||
"""
|
||||
return {
|
||||
"likes": self.stats.get("likes", 0) if self.stats else 0,
|
||||
"boosts": self.stats.get("boosts", 0) if self.stats else 0,
|
||||
"replies": self.stats.get("replies", 0) if self.stats else 0,
|
||||
}
|
||||
|
||||
### Async helpers ###
|
||||
|
||||
async def afetch_full(self) -> "Post":
|
||||
|
@ -461,6 +475,9 @@ class Post(StatorModel):
|
|||
if attachments:
|
||||
post.attachments.set(attachments)
|
||||
post.save()
|
||||
# Recalculate parent stats for replies
|
||||
if reply_to:
|
||||
reply_to.calculate_stats()
|
||||
return post
|
||||
|
||||
def edit_local(
|
||||
|
@ -516,6 +533,26 @@ class Post(StatorModel):
|
|||
)
|
||||
await tag.atransition_perform(HashtagStates.outdated)
|
||||
|
||||
def calculate_stats(self, save=True):
|
||||
"""
|
||||
Recalculates our stats dict
|
||||
"""
|
||||
from activities.models import PostInteraction, PostInteractionStates
|
||||
|
||||
self.stats = {
|
||||
"likes": self.interactions.filter(
|
||||
type=PostInteraction.Types.like,
|
||||
state__in=PostInteractionStates.group_active(),
|
||||
).count(),
|
||||
"boosts": self.interactions.filter(
|
||||
type=PostInteraction.Types.boost,
|
||||
state__in=PostInteractionStates.group_active(),
|
||||
).count(),
|
||||
"replies": Post.objects.filter(in_reply_to=self.object_uri).count(),
|
||||
}
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
### ActivityPub (outbound) ###
|
||||
|
||||
def to_ap(self) -> dict:
|
||||
|
@ -674,7 +711,7 @@ class Post(StatorModel):
|
|||
# Do we have one with the right ID?
|
||||
created = False
|
||||
try:
|
||||
post = cls.objects.select_related("author__domain").get(
|
||||
post: Post = cls.objects.select_related("author__domain").get(
|
||||
object_uri=data["id"]
|
||||
)
|
||||
except cls.DoesNotExist:
|
||||
|
@ -757,10 +794,18 @@ class Post(StatorModel):
|
|||
focal_x=focal_x,
|
||||
focal_y=focal_y,
|
||||
)
|
||||
# Calculate stats in case we have existing replies
|
||||
post.calculate_stats(save=False)
|
||||
post.save()
|
||||
# Potentially schedule a fetch of the reply parent
|
||||
# Potentially schedule a fetch of the reply parent, and recalculate
|
||||
# its stats if it's here already.
|
||||
if post.in_reply_to:
|
||||
cls.ensure_object_uri(post.in_reply_to)
|
||||
try:
|
||||
parent = cls.by_object_uri(post.in_reply_to)
|
||||
except cls.DoesNotExist:
|
||||
cls.ensure_object_uri(post.in_reply_to)
|
||||
else:
|
||||
parent.calculate_stats()
|
||||
return post
|
||||
|
||||
@classmethod
|
||||
|
@ -945,9 +990,9 @@ class Post(StatorModel):
|
|||
else []
|
||||
),
|
||||
"emojis": [emoji.to_mastodon_json() for emoji in self.emojis.usable()],
|
||||
"reblogs_count": self.interactions.filter(type="boost").count(),
|
||||
"favourites_count": self.interactions.filter(type="like").count(),
|
||||
"replies_count": 0,
|
||||
"reblogs_count": self.stats_with_defaults["boosts"],
|
||||
"favourites_count": self.stats_with_defaults["likes"],
|
||||
"replies_count": self.stats_with_defaults["replies"],
|
||||
"url": self.absolute_object_uri(),
|
||||
"in_reply_to_id": reply_parent.pk if reply_parent else None,
|
||||
"in_reply_to_account_id": reply_parent.author.pk if reply_parent else None,
|
||||
|
|
|
@ -302,6 +302,8 @@ class PostInteraction(StatorModel):
|
|||
TimelineEvent.add_post_interaction(interaction.post.author, interaction)
|
||||
# Force it into fanned_out as it's not ours
|
||||
interaction.transition_perform(PostInteractionStates.fanned_out)
|
||||
# Recalculate post stats
|
||||
interaction.post.calculate_stats()
|
||||
|
||||
@classmethod
|
||||
def handle_undo_ap(cls, data):
|
||||
|
@ -322,6 +324,8 @@ class PostInteraction(StatorModel):
|
|||
interaction.timeline_events.all().delete()
|
||||
# Force it into undone_fanned_out as it's not ours
|
||||
interaction.transition_perform(PostInteractionStates.undone_fanned_out)
|
||||
# Recalculate post stats
|
||||
interaction.post.calculate_stats()
|
||||
|
||||
### Mastodon API ###
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
from activities.models import (
|
||||
Post,
|
||||
PostInteraction,
|
||||
|
@ -31,22 +29,6 @@ class PostService:
|
|||
"author",
|
||||
"author__domain",
|
||||
)
|
||||
.annotate(
|
||||
like_count=models.Count(
|
||||
"interactions",
|
||||
filter=models.Q(
|
||||
interactions__type=PostInteraction.Types.like,
|
||||
interactions__state__in=PostInteractionStates.group_active(),
|
||||
),
|
||||
),
|
||||
boost_count=models.Count(
|
||||
"interactions",
|
||||
filter=models.Q(
|
||||
interactions__type=PostInteraction.Types.boost,
|
||||
interactions__state__in=PostInteractionStates.group_active(),
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, post: Post):
|
||||
|
@ -63,6 +45,7 @@ class PostService:
|
|||
)[0]
|
||||
if interaction.state not in PostInteractionStates.group_active():
|
||||
interaction.transition_perform(PostInteractionStates.new)
|
||||
self.post.calculate_stats()
|
||||
|
||||
def uninteract_as(self, identity, type):
|
||||
"""
|
||||
|
@ -74,6 +57,7 @@ class PostService:
|
|||
post=self.post,
|
||||
):
|
||||
interaction.transition_perform(PostInteractionStates.undone)
|
||||
self.post.calculate_stats()
|
||||
|
||||
def like_as(self, identity: Identity):
|
||||
self.interact_as(identity, PostInteraction.Types.like)
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
from django.db import models
|
||||
|
||||
from activities.models import (
|
||||
Hashtag,
|
||||
Post,
|
||||
PostInteraction,
|
||||
PostInteractionStates,
|
||||
TimelineEvent,
|
||||
)
|
||||
from activities.models import Hashtag, Post, TimelineEvent
|
||||
from activities.services import PostService
|
||||
from users.models import Identity
|
||||
|
||||
|
@ -21,38 +15,19 @@ class TimelineService:
|
|||
|
||||
@classmethod
|
||||
def event_queryset(cls):
|
||||
return (
|
||||
TimelineEvent.objects.select_related(
|
||||
"subject_post",
|
||||
"subject_post__author",
|
||||
"subject_post__author__domain",
|
||||
"subject_identity",
|
||||
"subject_identity__domain",
|
||||
"subject_post_interaction",
|
||||
"subject_post_interaction__identity",
|
||||
"subject_post_interaction__identity__domain",
|
||||
)
|
||||
.prefetch_related(
|
||||
"subject_post__attachments",
|
||||
"subject_post__mentions",
|
||||
"subject_post__emojis",
|
||||
)
|
||||
.annotate(
|
||||
like_count=models.Count(
|
||||
"subject_post__interactions",
|
||||
filter=models.Q(
|
||||
subject_post__interactions__type=PostInteraction.Types.like,
|
||||
subject_post__interactions__state__in=PostInteractionStates.group_active(),
|
||||
),
|
||||
),
|
||||
boost_count=models.Count(
|
||||
"subject_post__interactions",
|
||||
filter=models.Q(
|
||||
subject_post__interactions__type=PostInteraction.Types.boost,
|
||||
subject_post__interactions__state__in=PostInteractionStates.group_active(),
|
||||
),
|
||||
),
|
||||
)
|
||||
return TimelineEvent.objects.select_related(
|
||||
"subject_post",
|
||||
"subject_post__author",
|
||||
"subject_post__author__domain",
|
||||
"subject_identity",
|
||||
"subject_identity__domain",
|
||||
"subject_post_interaction",
|
||||
"subject_post_interaction__identity",
|
||||
"subject_post_interaction__identity__domain",
|
||||
).prefetch_related(
|
||||
"subject_post__attachments",
|
||||
"subject_post__mentions",
|
||||
"subject_post__emojis",
|
||||
)
|
||||
|
||||
def home(self) -> models.QuerySet[TimelineEvent]:
|
||||
|
|
|
@ -93,10 +93,8 @@ class Like(View):
|
|||
service = PostService(post)
|
||||
if self.undo:
|
||||
service.unlike_as(request.identity)
|
||||
post.like_count = max(0, post.like_count - 1)
|
||||
else:
|
||||
service.like_as(request.identity)
|
||||
post.like_count += 1
|
||||
# Return either a redirect or a HTMX snippet
|
||||
if request.htmx:
|
||||
return render(
|
||||
|
@ -127,10 +125,8 @@ class Boost(View):
|
|||
service = PostService(post)
|
||||
if self.undo:
|
||||
service.unboost_as(request.identity)
|
||||
post.boost_count = max(0, post.boost_count - 1)
|
||||
else:
|
||||
service.boost_as(request.identity)
|
||||
post.boost_count += 1
|
||||
# Return either a redirect or a HTMX snippet
|
||||
if request.htmx:
|
||||
return render(
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% if post.pk in interactions.boost %}
|
||||
<a title="Unboost" class="active" hx-post="{{ post.urls.action_unboost }}" hx-swap="outerHTML">
|
||||
<i class="fa-solid fa-retweet"></i>
|
||||
<span class="like-count">{% if event.boost_count is not None %}{{ event.boost_count }}{% else %}{{ post.boost_count }}{% endif %}</span>
|
||||
<span class="like-count">{{ post.stats_with_defaults.boosts }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a title="Boost" hx-post="{{ post.urls.action_boost }}" hx-swap="outerHTML">
|
||||
<i class="fa-solid fa-retweet"></i>
|
||||
<span class="like-count">{% if event.boost_count is not None %}{{ event.boost_count }}{% else %}{{ post.boost_count }}{% endif %}</span>
|
||||
<span class="like-count">{{ post.stats_with_defaults.boosts }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% if post.pk in interactions.like %}
|
||||
<a title="Unlike" class="active" hx-post="{{ post.urls.action_unlike }}" hx-swap="outerHTML" role="menuitem">
|
||||
<i class="fa-solid fa-star"></i>
|
||||
<span class="like-count">{% if event.like_count is not None %}{{ event.like_count }}{% else %}{{ post.like_count }}{% endif %}</span>
|
||||
<span class="like-count">{{ post.stats_with_defaults.likes }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<a title="Like" hx-post="{{ post.urls.action_like }}" hx-swap="outerHTML" role="menuitem">
|
||||
<i class="fa-solid fa-star"></i>
|
||||
<span class="like-count">{% if event.like_count is not None %}{{ event.like_count }}{% else %}{{ post.like_count }}{% endif %}</span>
|
||||
<span class="like-count">{{ post.stats_with_defaults.likes }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
<a title="Reply" href="{{ post.urls.action_reply }}" role="menuitem">
|
||||
<i class="fa-solid fa-reply"></i>
|
||||
<span class="like-count">{{ post.stats_with_defaults.replies }}</span>
|
||||
</a>
|
||||
|
|
|
@ -13,6 +13,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name="follow",
|
||||
name="boosts",
|
||||
field=models.BooleanField(default=True),
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="Also follow boosts from this user",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue