Просмотр исходного кода

#893: added validator for merge posts

Rafał Pitoń 7 лет назад
Родитель
Сommit
b5dc0ff92c

+ 18 - 5
misago/threads/api/postendpoints/merge.py

@@ -8,7 +8,7 @@ from misago.acl import add_acl
 from misago.conf import settings
 from misago.core.utils import clean_ids_list
 from misago.threads.permissions import allow_merge_post, exclude_invisible_posts
-from misago.threads.serializers import PostSerializer
+from misago.threads.serializers import MergePostsSerializer, PostSerializer
 
 
 MERGE_LIMIT = settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL
@@ -18,12 +18,25 @@ def posts_merge_endpoint(request, thread):
     if not thread.acl['can_merge_posts']:
         raise PermissionDenied(_("You can't merge posts in this thread."))
 
-    try:
-        posts = clean_posts_for_merge(request, thread)
-    except PermissionDenied as e:
-        return Response({'detail': six.text_type(e)}, status=400)
+    serializer = MergePostsSerializer(
+        data=request.data,
+        context={
+            'thread': thread,
+            'user': request.user,
+        },
+    )
+
+    if not serializer.is_valid():
+        return Response(
+            {
+                'detail': list(serializer.errors.values())[0][0],
+            },
+            status=400,
+        )
 
+    posts = serializer.posts_cache
     first_post, merged_posts = posts[0], posts[1:]
+
     for post in merged_posts:
         post.merge(first_post)
         post.delete()

+ 75 - 3
misago/threads/serializers/moderation.py

@@ -1,19 +1,91 @@
 from rest_framework import serializers
 
-from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext as _
+from django.core.exceptions import PermissionDenied, ValidationError
+from django.utils import six
+from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
 
 from misago.acl import add_acl
+from misago.conf import settings
 from misago.threads.models import Thread
-from misago.threads.permissions import can_start_thread
+from misago.threads.permissions import allow_merge_post, can_start_thread, exclude_invisible_posts
 from misago.threads.validators import validate_category, validate_title
 
 
+POSTS_MERGE_LIMIT = settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL
+
+
 __all__ = [
+    'MergePostsSerializer',
     'NewThreadSerializer',
 ]
 
 
+class MergePostsSerializer(serializers.Serializer):
+    posts = serializers.ListField(
+        child=serializers.IntegerField(
+            error_messages={
+                'invalid': ugettext_lazy("One or more post ids received were invalid."),
+            },
+        ),
+        error_messages={
+            'required': ugettext_lazy("You have to select at least two posts to merge."),
+        },
+    )
+
+    def validate_posts(self, data):
+        data = list(set(data))
+
+        if len(data) < 2:
+            raise serializers.ValidationError(_("You have to select at least two posts to merge."))
+        if len(data) > POSTS_MERGE_LIMIT:
+            message = ungettext(
+                "No more than %(limit)s post can be merged at single time.",
+                "No more than %(limit)s posts can be merged at single time.",
+                POSTS_MERGE_LIMIT,
+            )
+            raise serializers.ValidationError(message % {'limit': POSTS_MERGE_LIMIT})
+
+        user = self.context['user']
+        thread = self.context['thread']
+
+        posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
+        posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
+
+        posts = []
+        for post in posts_queryset:
+            post.category = thread.category
+            post.thread = thread
+
+            try:
+                allow_merge_post(user, post)
+            except PermissionDenied as e:
+                raise serializers.ValidationError(six.text_type(e))
+
+            if not posts:
+                posts.append(post)
+            else:
+                authorship_error = _("Posts made by different users can't be merged.")
+                if posts[0].poster_id:
+                    if post.poster_id != posts[0].poster_id:
+                        raise serializers.ValidationError(authorship_error)
+                else:
+                    if post.poster_id or post.poster_name != posts[0].poster_name:
+                        raise serializers.ValidationError(authorship_error)
+
+                if posts[0].pk != thread.first_post_id:
+                    if (posts[0].is_hidden != post.is_hidden or
+                            posts[0].is_unapproved != post.is_unapproved):
+                        raise serializers.ValidationError(_("Posts with different visibility can't be merged."))
+
+                posts.append(post)
+
+        if len(posts) != len(data):
+            raise serializers.ValidationError(_("One or more posts to merge could not be found."))
+
+        self.posts_cache = posts
+        return data
+
+
 class NewThreadSerializer(serializers.Serializer):
     title = serializers.CharField()
     category = serializers.IntegerField()

+ 20 - 4
misago/threads/tests/test_thread_postmerge_api.py

@@ -74,14 +74,30 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def test_empty_data(self):
         """api handles empty data"""
         response = self.client.post(
-            self.api_link,
-            json.dumps({}),
-            content_type="application/json",
+            self.api_link, json.dumps({}), content_type="application/json"
         )
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
         )
 
+    def test_invalid_data(self):
+        """api handles post that is invalid type"""
+        self.override_acl()
+        response = self.client.post(self.api_link, '[]', content_type="application/json")
+        self.assertContains(response, "Invalid data. Expected a dictionary", status_code=400)
+
+        self.override_acl()
+        response = self.client.post(self.api_link, '123', content_type="application/json")
+        self.assertContains(response, "Invalid data. Expected a dictionary", status_code=400)
+
+        self.override_acl()
+        response = self.client.post(self.api_link, '"string"', content_type="application/json")
+        self.assertContains(response, "Invalid data. Expected a dictionary", status_code=400)
+
+        self.override_acl()
+        response = self.client.post(self.api_link, 'malformed', content_type="application/json")
+        self.assertContains(response, "JSON parse error", status_code=400)
+
     def test_no_posts_ids(self):
         """api rejects no posts ids"""
         response = self.client.post(
@@ -105,7 +121,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             content_type="application/json",
         )
         self.assertContains(
-            response, "One or more post ids received were invalid.", status_code=400
+            response, "Expected a list of items but got type", status_code=400
         )
 
     def test_invalid_posts_ids(self):