Browse Source

likes api

Rafał Pitoń 8 years ago
parent
commit
6204246126

+ 1 - 1
misago/categories/migrations/0003_categories_roles.py

@@ -40,7 +40,7 @@ def create_default_categories_roles(apps, schema_editor):
             # threads perms
             # threads perms
             'misago.threads.permissions.threads': {
             'misago.threads.permissions.threads': {
                 'can_see_all_threads': 1,
                 'can_see_all_threads': 1,
-                'can_see_posts_likes': 2,
+                'can_see_posts_likes': 1,
                 'can_download_other_users_attachments': 1,
                 'can_download_other_users_attachments': 1,
                 'can_like_posts': 1
                 'can_like_posts': 1
             },
             },

+ 15 - 0
misago/threads/api/postendpoints/likes.py

@@ -0,0 +1,15 @@
+from rest_framework.response import Response
+
+from ...serializers import PostLikeSerializer
+
+
+def likes_list_endpoint(request, post):
+    queryset = post.postlike_set.values(
+        'user_id', 'user_name', 'user_slug', 'liked_on'
+    )
+
+    likes = []
+    for like in queryset.iterator():
+        likes.append(PostLikeSerializer(like).data)
+
+    return Response(likes)

+ 11 - 0
misago/threads/api/threadposts.py

@@ -20,6 +20,7 @@ from ..viewmodels.posts import ThreadPosts
 from ..viewmodels.thread import ForumThread
 from ..viewmodels.thread import ForumThread
 from .postingendpoint import PostingEndpoint
 from .postingendpoint import PostingEndpoint
 from .postendpoints.edits import get_edit_endpoint, revert_post_endpoint
 from .postendpoints.edits import get_edit_endpoint, revert_post_endpoint
+from .postendpoints.likes import likes_list_endpoint
 from .postendpoints.merge import posts_merge_endpoint
 from .postendpoints.merge import posts_merge_endpoint
 from .postendpoints.move import posts_move_endpoint
 from .postendpoints.move import posts_move_endpoint
 from .postendpoints.patch_event import event_patch_endpoint
 from .postendpoints.patch_event import event_patch_endpoint
@@ -266,6 +267,16 @@ class ViewSet(viewsets.ViewSet):
 
 
                 return revert_post_endpoint(request, post)
                 return revert_post_endpoint(request, post)
 
 
+    @detail_route(methods=['get'])
+    def likes(self, request, thread_pk, pk):
+        thread = self.get_thread(request, thread_pk)
+        post = self.get_post(request, thread, pk).model
+
+        if post.acl['can_see_likes'] < 2:
+            raise PermissionDenied(_("You can't see who liked this post."))
+
+        return likes_list_endpoint(request, post)
+
 
 
 class ThreadPostsViewSet(ViewSet):
 class ThreadPostsViewSet(ViewSet):
     thread = ForumThread
     thread = ForumThread

+ 3 - 0
misago/threads/models/post.py

@@ -141,6 +141,9 @@ class Post(models.Model):
     def get_api_url(self):
     def get_api_url(self):
         return self.thread_type.get_post_api_url(self)
         return self.thread_type.get_post_api_url(self)
 
 
+    def get_likes_api_url(self):
+        return self.thread_type.get_post_likes_api_url(self)
+
     def get_editor_api_url(self):
     def get_editor_api_url(self):
         return self.thread_type.get_post_editor_api_url(self)
         return self.thread_type.get_post_editor_api_url(self)
 
 

+ 1 - 0
misago/threads/serializers/__init__.py

@@ -2,6 +2,7 @@ from .moderation import *
 from .thread import *
 from .thread import *
 from .post import *
 from .post import *
 from .postedit import *
 from .postedit import *
+from .postlike import *
 from .attachment import *
 from .attachment import *
 from .poll import *
 from .poll import *
 from .pollvote import *
 from .pollvote import *

+ 1 - 0
misago/threads/serializers/post.py

@@ -97,6 +97,7 @@ class PostSerializer(serializers.ModelSerializer):
     def get_api(self, obj):
     def get_api(self, obj):
         return {
         return {
             'index': obj.get_api_url(),
             'index': obj.get_api_url(),
+            'likes': obj.get_likes_api_url(),
             'editor': obj.get_editor_api_url(),
             'editor': obj.get_editor_api_url(),
             'edits': obj.get_edits_api_url(),
             'edits': obj.get_edits_api_url(),
             'read': obj.get_read_api_url(),
             'read': obj.get_read_api_url(),

+ 43 - 0
misago/threads/serializers/postlike.py

@@ -0,0 +1,43 @@
+from django.core.urlresolvers import reverse
+
+from rest_framework import serializers
+
+from ..models import PostLike
+
+
+__all__ = [
+    'PostLikeSerializer',
+]
+
+
+class PostLikeSerializer(serializers.ModelSerializer):
+    id = serializers.SerializerMethodField()
+    username = serializers.SerializerMethodField()
+
+    url = serializers.SerializerMethodField()
+
+    class Meta:
+        model = PostLike
+        fields = (
+            'liked_on',
+
+            'id',
+            'username',
+
+            'url',
+        )
+
+    def get_id(self, obj):
+        return obj['user_id']
+
+    def get_username(self, obj):
+        return obj['user_name']
+
+    def get_url(self, obj):
+        if obj['user_id']:
+            return reverse('misago:user', kwargs={
+                'slug': obj['user_slug'],
+                'pk': obj['user_id'],
+            })
+        else:
+            return None

+ 0 - 2
misago/threads/tests/test_thread_postedits_api.py

@@ -18,8 +18,6 @@ class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
     def setUp(self):
     def setUp(self):
         super(ThreadPostEditsApiTestCase, self).setUp()
         super(ThreadPostEditsApiTestCase, self).setUp()
 
 
-        self.category = Category.objects.get(slug='first-category')
-        self.thread = testutils.post_thread(category=self.category)
         self.post = testutils.reply_thread(self.thread, poster=self.user)
         self.post = testutils.reply_thread(self.thread, poster=self.user)
 
 
         self.api_link = reverse('misago:api:thread-post-edits', kwargs={
         self.api_link = reverse('misago:api:thread-post-edits', kwargs={

+ 56 - 0
misago/threads/tests/test_thread_postlikes_api.py

@@ -0,0 +1,56 @@
+from django.core.urlresolvers import reverse
+
+from .. import testutils
+from ..serializers import PostLikeSerializer
+from .test_threads_api import ThreadsApiTestCase
+
+
+class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
+    def setUp(self):
+        super(ThreadPostLikesApiTestCase, self).setUp()
+
+        self.post = testutils.reply_thread(self.thread, poster=self.user)
+
+        self.api_link = reverse('misago:api:thread-post-likes', kwargs={
+            'thread_pk': self.thread.pk,
+            'pk': self.post.pk
+        })
+
+    def test_no_permission(self):
+        """api errors if user has no permission to see likes"""
+        self.override_acl({
+            'can_see_posts_likes': 0
+        })
+
+        response = self.client.get(self.api_link)
+        self.assertContains(response, "You can't see who liked this post.", status_code=403)
+
+    def test_no_permission_to_list(self):
+        """api errors if user has no permission to see likes, but can see likes count"""
+        self.override_acl({
+            'can_see_posts_likes': 1
+        })
+
+        response = self.client.get(self.api_link)
+        self.assertContains(response, "You can't see who liked this post.", status_code=403)
+
+    def test_no_likes(self):
+        """api returns empty list if post has no likes"""
+        response = self.client.get(self.api_link)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.json(), [])
+
+    def test_likes(self):
+        """api returns list of likes"""
+        like = testutils.like_post(self.user, self.post)
+        other_like = testutils.like_post(self.user, self.post)
+
+        other_like.user = None
+        other_like.save()
+
+        response = self.client.get(self.api_link)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.json(), [
+            PostLikeSerializer(other_like.__dict__).data,
+            PostLikeSerializer(like.__dict__).data,
+        ])

+ 2 - 21
misago/threads/tests/test_thread_postpatch_api.py

@@ -622,25 +622,6 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
 
 
 
 
 class PostLikeApiTests(ThreadPostPatchApiTestCase):
 class PostLikeApiTests(ThreadPostPatchApiTestCase):
-    def like_post(self):
-        self.post.postlike_set.create(
-            category=self.category,
-            thread=self.thread,
-            user=self.user,
-            user_name=self.user.username,
-            user_slug=self.user.slug,
-            user_ip='127.0.0.1'
-        )
-        self.post.likes += 1
-        self.post.last_likes = [
-            {
-                'username': self.user.username,
-                'slug': self.user.slug,
-                'url': self.user.get_absolute_url()
-            }
-        ]
-        self.post.save()
-
     def test_like_no_see_permission(self):
     def test_like_no_see_permission(self):
         """api validates user's permission to see posts likes"""
         """api validates user's permission to see posts likes"""
         self.override_acl({
         self.override_acl({
@@ -687,7 +668,7 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
 
     def test_unlike_post(self):
     def test_unlike_post(self):
         """api removes user like from post"""
         """api removes user like from post"""
-        self.like_post()
+        testutils.like_post(self.user, self.post)
 
 
         response = self.patch(self.api_link, [
         response = self.patch(self.api_link, [
             {'op': 'replace', 'path': 'is-liked', 'value': False}
             {'op': 'replace', 'path': 'is-liked', 'value': False}
@@ -705,7 +686,7 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
 
     def test_like_post_no_change(self):
     def test_like_post_no_change(self):
         """api does no state change if we are linking liked post"""
         """api does no state change if we are linking liked post"""
-        self.like_post()
+        testutils.like_post(self.user, self.post)
 
 
         response = self.patch(self.api_link, [
         response = self.patch(self.api_link, [
             {'op': 'replace', 'path': 'is-liked', 'value': True}
             {'op': 'replace', 'path': 'is-liked', 'value': True}

+ 22 - 0
misago/threads/testutils.py

@@ -181,3 +181,25 @@ def post_poll(thread, poster):
     )
     )
 
 
     return poll
     return poll
+
+
+def like_post(user, post):
+    like = post.postlike_set.create(
+        category=post.category,
+        thread=post.thread,
+        user=user,
+        user_name=user.username,
+        user_slug=user.slug,
+        user_ip='127.0.0.1'
+    )
+    post.likes += 1
+    post.last_likes = [
+        {
+            'username': user.username,
+            'slug': user.slug,
+            'url': user.get_absolute_url()
+        }
+    ]
+    post.save()
+
+    return like

+ 6 - 0
misago/threads/threadtypes/thread.py

@@ -135,6 +135,12 @@ class Thread(ThreadType):
             'pk': post.pk
             'pk': post.pk
         })
         })
 
 
+    def get_post_likes_api_url(self, post):
+        return reverse('misago:api:thread-post-likes', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })
+
     def get_post_editor_api_url(self, post):
     def get_post_editor_api_url(self, post):
         return reverse('misago:api:thread-post-editor', kwargs={
         return reverse('misago:api:thread-post-editor', kwargs={
             'thread_pk': post.thread_id,
             'thread_pk': post.thread_id,