From f907143c67c06c54844b36b184ed8019bd5209e9 Mon Sep 17 00:00:00 2001 From: falbru Date: Thu, 28 Mar 2024 12:07:38 +0100 Subject: [PATCH 1/4] Add reactions to comments --- lego/apps/comments/models.py | 28 +++++++++++++++++++++++++++- lego/apps/comments/serializers.py | 11 ++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lego/apps/comments/models.py b/lego/apps/comments/models.py index e2c04de53..c1135a629 100644 --- a/lego/apps/comments/models.py +++ b/lego/apps/comments/models.py @@ -1,9 +1,10 @@ -from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models from lego.apps.comments.permissions import CommentPermissionHandler from lego.apps.content.fields import ContentField +from lego.apps.reactions.models import Reaction from lego.utils.managers import BasisModelManager from lego.utils.models import BasisModel @@ -19,6 +20,7 @@ class Comment(BasisModel): object_id = models.PositiveIntegerField(db_index=True) content_object = GenericForeignKey() parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE) + reactions = GenericRelation(Reaction) objects = CommentManager() # type: ignore @@ -34,5 +36,29 @@ def delete(self): else: super(Comment, self).delete(force=True) + def get_reactions_grouped(self, user): + grouped = {} + for reaction in self.reactions.all(): + if reaction.emoji.pk not in grouped: + grouped[reaction.emoji.pk] = { + "emoji": reaction.emoji.pk, + "unicode_string": reaction.emoji.unicode_string, + "count": 0, + "has_reacted": False, + "reaction_id": None, + } + + grouped[reaction.emoji.pk]["count"] += 1 + + if reaction.created_by == user: + grouped[reaction.emoji.pk]["has_reacted"] = True + grouped[reaction.emoji.pk]["reaction_id"] = reaction.id + + return sorted(grouped.values(), key=lambda kv: kv["count"], reverse=True) + def __str__(self): return self.text + + @property + def content_self(self): + return f"{self._meta.app_label}.{self._meta.model_name}-{self.pk}" diff --git a/lego/apps/comments/serializers.py b/lego/apps/comments/serializers.py index 5eee62b6f..7422aa875 100644 --- a/lego/apps/comments/serializers.py +++ b/lego/apps/comments/serializers.py @@ -1,6 +1,7 @@ from django.contrib.contenttypes.models import ContentType +from rest_framework import serializers from rest_framework.exceptions import ValidationError -from rest_framework.fields import DateTimeField +from rest_framework.fields import CharField, DateTimeField from lego.apps.comments.models import Comment from lego.apps.content.fields import ContentSerializerField @@ -14,6 +15,8 @@ class CommentSerializer(BasisModelSerializer): updated_at = DateTimeField(read_only=True) content_target = GenericRelationField(source="content_object") text = ContentSerializerField() + reactions_grouped = serializers.SerializerMethodField() + content_self = CharField(read_only=True) class Meta: model = Comment @@ -22,11 +25,17 @@ class Meta: "text", "author", "content_target", + "content_self", "created_at", "updated_at", "parent", + "reactions_grouped", ) + def get_reactions_grouped(self, obj): + user = self.context["request"].user + return obj.get_reactions_grouped(user) + def validate(self, attrs): content_target = attrs.get("content_object") From 9d56212590544ffbcdf2679f943a4c7d42445c33 Mon Sep 17 00:00:00 2001 From: falbru Date: Thu, 11 Apr 2024 20:50:30 +0200 Subject: [PATCH 2/4] Change name of content_self to content_target_self --- lego/apps/comments/models.py | 2 +- lego/apps/comments/serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lego/apps/comments/models.py b/lego/apps/comments/models.py index c1135a629..321bdf2f6 100644 --- a/lego/apps/comments/models.py +++ b/lego/apps/comments/models.py @@ -60,5 +60,5 @@ def __str__(self): return self.text @property - def content_self(self): + def content_target_self(self): return f"{self._meta.app_label}.{self._meta.model_name}-{self.pk}" diff --git a/lego/apps/comments/serializers.py b/lego/apps/comments/serializers.py index 7422aa875..81f7d21ff 100644 --- a/lego/apps/comments/serializers.py +++ b/lego/apps/comments/serializers.py @@ -16,7 +16,7 @@ class CommentSerializer(BasisModelSerializer): content_target = GenericRelationField(source="content_object") text = ContentSerializerField() reactions_grouped = serializers.SerializerMethodField() - content_self = CharField(read_only=True) + content_target_self = CharField(read_only=True) class Meta: model = Comment @@ -25,7 +25,7 @@ class Meta: "text", "author", "content_target", - "content_self", + "content_target_self", "created_at", "updated_at", "parent", From 01501cc15e7bb831cafc44117c0e575b25e6e19a Mon Sep 17 00:00:00 2001 From: Jonatan Norbye Date: Mon, 20 May 2024 00:11:06 +1200 Subject: [PATCH 3/4] Disable logging if analytics key is not set --- lego/apps/stats/analytics_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lego/apps/stats/analytics_client.py b/lego/apps/stats/analytics_client.py index e7da44a12..0a20c1026 100644 --- a/lego/apps/stats/analytics_client.py +++ b/lego/apps/stats/analytics_client.py @@ -20,6 +20,9 @@ def setup_analytics(): write_key = getattr(settings, "ANALYTICS_WRITE_KEY", "") host = getattr(settings, "ANALYTICS_HOST", "https://api.segment.io") + if write_key == "": + return + production = getattr(settings, "ENVIRONMENT_NAME", None) == "production" send = not (development or getattr(settings, "TESTING", False)) or production @@ -36,6 +39,9 @@ def setup_analytics(): def _proxy(method, user, *args, **kwargs): global default_client, development + if default_client is None: + return + fn = getattr(default_client, method) kwargs["context"] = { From c324a04141c7eacfb75ec668fc706525195edac9 Mon Sep 17 00:00:00 2001 From: falbru Date: Wed, 22 May 2024 22:45:24 +0200 Subject: [PATCH 4/4] Add tests for reactions on comments --- lego/apps/comments/tests/test_reactions.py | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 lego/apps/comments/tests/test_reactions.py diff --git a/lego/apps/comments/tests/test_reactions.py b/lego/apps/comments/tests/test_reactions.py new file mode 100644 index 000000000..6db2824b8 --- /dev/null +++ b/lego/apps/comments/tests/test_reactions.py @@ -0,0 +1,56 @@ +from lego.apps.articles.models import Article +from lego.apps.comments.models import Comment +from lego.apps.emojis.models import Emoji +from lego.apps.reactions.models import Reaction +from lego.apps.users.models import User +from lego.utils.test_utils import BaseTestCase + + +class CommentReactionsTestCase(BaseTestCase): + fixtures = [ + "test_abakus_groups.yaml", + "test_users.yaml", + "test_emojis.yaml", + "test_articles.yaml", + ] + + def setUp(self): + self.comment = Comment.objects.create( + created_by_id=0, + text="first comment", + content_object=Article.objects.all().first(), + ) + self.user = User.objects.all().first() + self.comment.created_by = self.user + self.comment.save() + + self.emoji = Emoji.objects.first() + + def test_add_reaction(self): + test_reaction = Reaction.objects.create( + content_object=self.comment, emoji=self.emoji + ) + test_reaction.created_by = self.user + test_reaction.save() + + reactions_grouped = self.comment.get_reactions_grouped(self.user) + self.assertEqual(len(reactions_grouped), 1) + self.assertEqual(reactions_grouped[0]["count"], 1) + self.assertEqual(reactions_grouped[0]["has_reacted"], True) + self.assertEqual( + reactions_grouped[0]["unicode_string"], self.emoji.unicode_string + ) + + def test_remove_reaction(self): + test_reaction = Reaction.objects.create( + content_object=self.comment, emoji=self.emoji + ) + + reactions_grouped = self.comment.get_reactions_grouped(self.user) + self.assertEqual(len(reactions_grouped), 1) + self.assertEqual(reactions_grouped[0]["count"], 1) + + test_reaction.delete() + + reactions_grouped = self.comment.get_reactions_grouped(self.user) + self.assertEqual(len(reactions_grouped), 0)