Skip to content

Commit

Permalink
Ajout des likes/dislikes dans les messages privés (#6317)
Browse files Browse the repository at this point in the history
  • Loading branch information
Migwel authored Jun 16, 2022
1 parent ec43b7e commit 7b8d466
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 3 deletions.
4 changes: 4 additions & 0 deletions templates/mp/topic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
{% endcaptureas %}
{% endif %}

{% captureas karma_link %}
{% url "api:mp:mp-reaction-karma" topic.pk message.pk %}
{% endcaptureas %}

{% captureas unread_link %}
{% url "private-post-unread" %}?message={{ message.pk }}
{% endcaptureas %}
Expand Down
3 changes: 3 additions & 0 deletions zds/forum/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ def test_get_post_voters(self):
self.assertEqual(1, response.data["dislike"]["count"])

# Now we change the settings to keep anonymous the first [dis]like
previous_limit = settings.VOTES_ID_LIMIT
settings.VOTES_ID_LIMIT = anon_limit.pk
# and we run the same tests
# on first message we should see 1 like and 1 anonymous
Expand All @@ -193,3 +194,5 @@ def test_get_post_voters(self):
self.assertEqual(1, len(response.data["dislike"]["users"]))
self.assertEqual(1, response.data["like"]["count"])
self.assertEqual(1, response.data["dislike"]["count"])

settings.VOTES_ID_LIMIT = previous_limit
31 changes: 31 additions & 0 deletions zds/mp/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
from django.shortcuts import get_object_or_404
from dry_rest_permissions.generics import DRYPermissionsField
from rest_framework import serializers
from rest_framework.fields import IntegerField
from rest_framework.serializers import ModelSerializer

from zds.api.serializers import ZdSModelSerializer

from zds.member.api.serializers import UserListSerializer
from zds.mp.commons import UpdatePrivatePost
from zds.mp.models import PrivateTopic, PrivatePost
from zds.mp.validators import ParticipantsUserValidator, TitleValidator, TextValidator
from zds.mp.utils import send_mp, send_message_mp
from zds.utils.api.serializers import KarmaSerializer


class PrivatePostSerializer(ZdSModelSerializer):
Expand Down Expand Up @@ -189,3 +193,30 @@ def update(self, instance, validated_data):

def throw_error(self, key=None, message=None):
raise serializers.ValidationError(message)


class PrivatePostLikesSerializer(ModelSerializer):
count = IntegerField(source="like", read_only=True)
users = UserListSerializer(source="get_likers", many=True, read_only=True)

class Meta:
model = PrivatePost
fields = ("count", "users")


class PrivatePostDislikesSerializer(ModelSerializer):
count = IntegerField(source="dislike", read_only=True)
users = UserListSerializer(source="get_dislikers", many=True, read_only=True)

class Meta:
model = PrivatePost
fields = ("count", "users")


class PrivatePostKarmaSerializer(KarmaSerializer):
like = PrivatePostLikesSerializer(source="*", read_only=True)
dislike = PrivatePostDislikesSerializer(source="*", read_only=True)

class Meta:
model = PrivatePost
fields = ("like", "dislike", "user", "vote")
157 changes: 156 additions & 1 deletion zds/mp/api/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from zds.member.api.tests import create_oauth2_client, authenticate_client
from zds.member.tests.factories import ProfileFactory, UserFactory
from zds.mp.tests.factories import PrivateTopicFactory, PrivatePostFactory
from zds.mp.models import PrivateTopic
from zds.mp.models import PrivateTopic, PrivatePostVote


class PrivateTopicListAPITest(APITestCase):
Expand Down Expand Up @@ -1150,3 +1150,158 @@ def test_has_not_update_permission_for_authenticated_users_and_but_not_author_fo
response = self.client.get(reverse("api:mp:message-detail", args=[self.private_topic.id, self.private_post.id]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data.get("permissions").get("update"))


class PrivateTopicKarmaAPITest(APITestCase):
def setUp(self):
self.profile = ProfileFactory()
self.private_topic = PrivateTopicFactory(author=self.profile.user)
self.private_post = PrivatePostFactory(
author=self.profile.user, privatetopic=self.private_topic, position_in_topic=1
)
self.client = APIClient()
client_oauth2 = create_oauth2_client(self.profile.user)
authenticate_client(self.client, client_oauth2, self.profile.user.username, "hostel77")

caches[extensions_api_settings.DEFAULT_USE_CACHE].clear()

def test_karma_of_private_post_not_in_participants(self):
"""
Gets an error 404 when the member doesn't have permission to display karma about the private post.
"""
another_profile = ProfileFactory()
another_private_topic = PrivateTopicFactory(author=another_profile.user)
another_private_post = PrivatePostFactory(
author=self.profile.user, privatetopic=another_private_topic, position_in_topic=1
)

response = self.client.get(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, another_private_post.id])
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_like_on_private_post(self):
"""
Add thumbs up to private post
"""
another_profile = ProfileFactory()
another_private_topic = PrivateTopicFactory(author=another_profile.user)
another_private_post = PrivatePostFactory(
author=another_profile.user, privatetopic=another_private_topic, position_in_topic=1
)
another_private_topic.participants.add(self.profile.user)

response = self.client.put(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, another_private_post.id]),
{"vote": "like"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(
PrivatePostVote.objects.filter(
user=self.profile.user, private_post=another_private_post, positive=True
).exists()
)

def test_dislike_on_private_post(self):
"""
Add thumbs down to private post
"""
another_profile = ProfileFactory()
another_private_topic = PrivateTopicFactory(author=another_profile.user)
another_private_post = PrivatePostFactory(
author=another_profile.user, privatetopic=another_private_topic, position_in_topic=1
)
another_private_topic.participants.add(self.profile.user)

response = self.client.put(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, another_private_post.id]),
{"vote": "dislike"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(
PrivatePostVote.objects.filter(
user=self.profile.user, private_post=another_private_post, positive=False
).exists()
)

def test_like_then_cancel_on_private_post(self):
"""
Add thumbs up to private post then cancels it
"""
another_profile = ProfileFactory()
another_private_topic = PrivateTopicFactory(author=another_profile.user)
another_private_post = PrivatePostFactory(
author=another_profile.user, privatetopic=another_private_topic, position_in_topic=1
)
another_private_topic.participants.add(self.profile.user)

response = self.client.put(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, another_private_post.id]),
{"vote": "like"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.put(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, another_private_post.id]),
{"vote": "neutral"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(
PrivatePostVote.objects.filter(
user=self.profile.user, private_post=another_private_post, positive=True
).exists()
)

def test_get_private_post_voters(self):
"""
Verify that likes and dislikes voters can be retrieved
"""
another_profile1 = ProfileFactory()
another_profile2 = ProfileFactory()
another_private_topic = PrivateTopicFactory(author=another_profile1.user)
another_private_topic.participants.add(self.profile.user)
another_private_topic.participants.add(another_profile2.user)

upvoted_post = PrivatePostFactory(
author=another_profile1.user, privatetopic=another_private_topic, position_in_topic=1, like=2
)
PrivatePostVote.objects.create(user=self.profile.user, private_post=upvoted_post, positive=True)
PrivatePostVote.objects.create(user=another_profile2.user, private_post=upvoted_post, positive=True)

downvoted_post = PrivatePostFactory(
author=another_profile1.user, privatetopic=another_private_topic, position_in_topic=2, dislike=2
)
PrivatePostVote.objects.create(user=self.profile.user, private_post=downvoted_post, positive=False)
PrivatePostVote.objects.create(user=another_profile2.user, private_post=downvoted_post, positive=False)

neutral_post = PrivatePostFactory(
author=another_profile1.user, privatetopic=another_private_topic, position_in_topic=3, like=1, dislike=1
)
PrivatePostVote.objects.create(user=self.profile.user, private_post=neutral_post, positive=True)
PrivatePostVote.objects.create(user=another_profile2.user, private_post=neutral_post, positive=False)

response = self.client.get(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, upvoted_post.id])
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(2, len(response.data["like"]["users"]))
self.assertEqual(0, len(response.data["dislike"]["users"]))
self.assertEqual(2, response.data["like"]["count"])
self.assertEqual(0, response.data["dislike"]["count"])

response = self.client.get(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, downvoted_post.id])
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(0, len(response.data["like"]["users"]))
self.assertEqual(2, len(response.data["dislike"]["users"]))
self.assertEqual(0, response.data["like"]["count"])
self.assertEqual(2, response.data["dislike"]["count"])

response = self.client.get(
reverse("api:mp:mp-reaction-karma", args=[another_private_topic.id, neutral_post.id])
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(1, len(response.data["like"]["users"]))
self.assertEqual(1, len(response.data["dislike"]["users"]))
self.assertEqual(1, response.data["like"]["count"])
self.assertEqual(1, response.data["dislike"]["count"])
8 changes: 7 additions & 1 deletion zds/mp/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from django.urls import re_path
from django.urls import re_path, path

from zds.mp.api.views import (
PrivateTopicListAPI,
PrivateTopicDetailAPI,
PrivatePostListAPI,
PrivatePostDetailAPI,
PrivateTopicReadAPI,
PrivatePostReactionKarmaView,
)

urlpatterns = [
Expand All @@ -15,5 +16,10 @@
re_path(
r"^(?P<pk_ptopic>[0-9]+)/messages/(?P<pk>[0-9]+)/?$", PrivatePostDetailAPI.as_view(), name="message-detail"
),
re_path(
r"^(?P<pk_ptopic>[0-9]+)/messages/(?P<pk>[0-9]+)/karma/$",
PrivatePostReactionKarmaView.as_view(),
name="mp-reaction-karma",
),
re_path(r"^unread/$", PrivateTopicReadAPI.as_view(), name="list-unread"),
]
9 changes: 8 additions & 1 deletion zds/mp/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
RetrieveUpdateAPIView,
ListAPIView,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework_extensions.cache.decorators import cache_response
from rest_framework_extensions.etag.decorators import etag
Expand All @@ -36,6 +36,7 @@
PrivateTopicCreateSerializer,
PrivatePostSerializer,
PrivatePostActionSerializer,
PrivatePostKarmaSerializer,
)
from zds.mp.commons import LeavePrivateTopic
from zds.mp.models import PrivateTopic, PrivatePost, mark_read
Expand Down Expand Up @@ -579,3 +580,9 @@ def get_queryset(self):
subscription__content_type=ContentType.objects.get_for_model(PrivateTopic)
)
return [notification.content_object.privatetopic for notification in notifications]


class PrivatePostReactionKarmaView(RetrieveUpdateDestroyAPIView):
queryset = PrivatePost.objects.all()
serializer_class = PrivatePostKarmaSerializer
permission_classes = (IsAuthenticated, IsParticipantFromPrivatePost, DRYPermissions)
40 changes: 40 additions & 0 deletions zds/mp/migrations/0007_add_votes_to_private_post.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Generated by Django 3.2.12 on 2022-05-22 21:02

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("mp", "0006_auto_20190114_1301"),
]

operations = [
migrations.AddField(
model_name="privatepost",
name="dislike",
field=models.IntegerField(default=0, verbose_name="Dislikes"),
),
migrations.AddField(
model_name="privatepost",
name="like",
field=models.IntegerField(default=0, verbose_name="Likes"),
),
migrations.CreateModel(
name="PrivatePostVote",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("positive", models.BooleanField(default=True, verbose_name="Est un vote positif")),
("private_post", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="mp.privatepost")),
("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
"verbose_name": "Vote",
"verbose_name_plural": "Votes",
"unique_together": {("user", "private_post")},
},
),
]
Loading

0 comments on commit 7b8d466

Please sign in to comment.