Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ajout des likes/dislikes dans les messages privés #6317

Merged
merged 3 commits into from
Jun 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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