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

+ 1 - 1
misago/threads/api/postingendpoint/category.py

@@ -17,7 +17,7 @@ class CategoryMiddleware(PostingMiddleware):
     Middleware that validates category id and sets category on thread and post instances
     """
     def use_this_middleware(self):
-        return self.tree_name == THREADS_ROOT_NAME and self.mode == PostingEndpoint.START
+        return self.mode == PostingEndpoint.START and self.tree_name == THREADS_ROOT_NAME
 
     def get_serializer(self):
         return CategorySerializer(self.user, data=self.request.data)

+ 4 - 2
misago/threads/api/postingendpoint/reply.py

@@ -27,7 +27,6 @@ class ReplyMiddleware(PostingMiddleware):
             self.edit_post(serializer.validated_data, parsing_result)
         else:
             self.new_post(serializer.validated_data, parsing_result)
-            self.thread.set_first_post(self.post)
 
         self.post.updated_on = self.datetime
         self.post.save()
@@ -35,8 +34,11 @@ class ReplyMiddleware(PostingMiddleware):
         update_post_checksum(self.post)
         self.post.update_fields.append('checksum')
 
+        if self.mode == PostingEndpoint.START:
+            self.thread.set_first_post(self.post)
         if self.mode != PostingEndpoint.EDIT:
             self.thread.set_last_post(self.post)
+
         self.thread.save()
 
     def new_thread(self, validated_data):
@@ -73,7 +75,7 @@ class ReplySerializer(serializers.Serializer):
     post = serializers.CharField(
         validators=[validate_post],
         error_messages={
-            'required': ugettext_lazy("You have to enter a message")
+            'required': ugettext_lazy("You have to enter a message.")
         }
     )
 

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

@@ -6,11 +6,15 @@ from rest_framework.decorators import detail_route, list_route
 from rest_framework.response import Response
 
 from misago.core.shortcuts import get_int_or_404
+from misago.users.online.utils import make_users_status_aware
 
+from ..models import Post
 from ..permissions.threads import allow_edit_post, allow_reply_thread
+from ..serializers import PostSerializer
 from ..viewmodels.post import ThreadPost
 from ..viewmodels.posts import ThreadPosts
 from ..viewmodels.thread import ForumThread
+from .postingendpoint import PostingEndpoint
 
 
 class ViewSet(viewsets.ViewSet):
@@ -27,12 +31,52 @@ class ViewSet(viewsets.ViewSet):
         thread = self.thread(request, get_int_or_404(thread_pk))
         allow_reply_thread(request.user, thread.thread)
 
+        post = Post(thread=thread.thread, category=thread.category)
+
+        # Put them through posting pipeline
+        posting = PostingEndpoint(
+            request,
+            PostingEndpoint.REPLY,
+            thread=thread.thread,
+            post=post
+        )
+
+        if posting.is_valid():
+            user_posts = request.user.posts
+
+            posting.save()
+
+            # setup extra data for serialization
+            post.is_read = False
+            post.is_new = True
+            post.poster.posts = user_posts + 1
+
+            make_users_status_aware(request.user, [post.poster])
+
+            return Response(PostSerializer(post).data)
+        else:
+            return Response(posting.errors, status=400)
+
     def update(self, request, thread_pk, pk):
         thread = self.thread(request, get_int_or_404(thread_pk))
         post = ThreadPost(request, thread, get_int_or_404(pk)).post
 
         allow_edit_post(request.user, post)
 
+        posting = PostingEndpoint(
+            request,
+            PostingEndpoint.EDIT,
+            thread=thread.thread,
+            post=post
+        )
+
+        if posting.is_valid():
+            raise NotImplementedError("NOT YET!")
+        else:
+            return Response(posting.errors, status=400)
+
+        return Response({})
+
     def list(self, request, thread_pk):
         page = get_int_or_404(request.query_params.get('page', 0))
         if page == 1:

+ 189 - 0
misago/threads/tests/test_thread_reply_api.py

@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import json
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.utils.encoding import smart_str
+
+from misago.acl.testutils import override_acl
+from misago.categories.models import Category
+from misago.users.testutils import AuthenticatedUserTestCase
+
+from .. import testutils
+from ..models import Thread
+from ..threadtypes import trees_map
+
+
+class ReplyThreadTests(AuthenticatedUserTestCase):
+    def setUp(self):
+        super(ReplyThreadTests, self).setUp()
+
+        self.category = Category.objects.get(slug='first-category')
+        self.thread = testutils.post_thread(category=self.category)
+
+        self.api_link = reverse('misago:api:thread-post-list', kwargs={
+            'thread_pk': self.thread.pk
+        })
+
+    def override_acl(self, extra_acl=None):
+        new_acl = self.user.acl
+        new_acl['categories'][self.category.pk].update({
+            'can_see': 1,
+            'can_browse': 1,
+            'can_start_threads': 0,
+            'can_reply_threads': 1
+        })
+
+        if extra_acl:
+            new_acl['categories'][self.category.pk].update(extra_acl)
+
+        override_acl(self.user, new_acl)
+
+    def test_cant_reply_thread_as_guest(self):
+        """user has to be authenticated to be able to post thread"""
+        self.logout_user()
+
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 403)
+
+    def test_thread_visibility(self):
+        """thread's visibility is validated"""
+        self.override_acl({'can_see': 0})
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 404)
+
+        self.override_acl({'can_browse': 0})
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 404)
+
+        self.override_acl({'can_see_all_threads': 0})
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 404)
+
+    def test_cant_reply_thread(self):
+        """permission to reply thread is validated"""
+        self.override_acl({
+            'can_reply_threads': 0
+        })
+
+        response = self.client.post(self.api_link)
+        self.assertContains(response, "You can't reply to threads in this category.", status_code=403)
+
+    def test_closed_category(self):
+        """permssion to reply in closed category is validated"""
+        self.override_acl({
+            'can_reply_threads': 1,
+            'can_close_threads': 0
+        })
+
+        self.category.is_closed = True
+        self.category.save()
+
+        response = self.client.post(self.api_link)
+        self.assertContains(response, "This category is closed. You can't reply to threads in it.", status_code=403)
+
+        # allow to post in closed category
+        self.override_acl({
+            'can_reply_threads': 1,
+            'can_close_threads': 1
+        })
+
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 400)
+
+    def test_closed_thread(self):
+        """permssion to reply in closed thread is validated"""
+        self.override_acl({
+            'can_reply_threads': 1,
+            'can_close_threads': 0
+        })
+
+        self.thread.is_closed = True
+        self.thread.save()
+
+        response = self.client.post(self.api_link)
+        self.assertContains(response, "You can't reply to closed threads in this category.", status_code=403)
+
+        # allow to post in closed thread
+        self.override_acl({
+            'can_reply_threads': 1,
+            'can_close_threads': 1
+        })
+
+        response = self.client.post(self.api_link)
+        self.assertEqual(response.status_code, 400)
+
+    def test_empty_data(self):
+        """no data sent handling has no showstoppers"""
+        self.override_acl()
+
+        response = self.client.post(self.api_link, data={})
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(json.loads(smart_str(response.content)), {
+            'post': [
+                "You have to enter a message."
+            ]
+        })
+
+    def test_post_is_validated(self):
+        """post is validated"""
+        self.override_acl()
+
+        response = self.client.post(self.api_link, data={
+            'post': "a",
+        })
+
+        self.assertEqual(response.status_code, 400)
+        self.assertEqual(json.loads(smart_str(response.content)), {
+            'post': [
+                "Posted message should be at least 5 characters long (it has 1)."
+            ]
+        })
+
+    def test_can_reply_thread(self):
+        """endpoint creates new reply"""
+        self.override_acl()
+        response = self.client.post(self.api_link, data={
+            'post': "This is test response!"
+        })
+        self.assertEqual(response.status_code, 200)
+
+        thread = Thread.objects.get(pk=self.thread.pk)
+
+        self.override_acl()
+        response = self.client.get(self.thread.get_absolute_url())
+        self.assertContains(response, "<p>This is test response!</p>")
+
+        self.reload_user()
+        self.assertEqual(self.user.posts, 1)
+
+        post = self.user.post_set.all()[:1][0]
+        self.assertEqual(post.category_id, self.category.pk)
+        self.assertEqual(post.original, "This is test response!")
+        self.assertEqual(post.poster_id, self.user.id)
+        self.assertEqual(post.poster_name, self.user.username)
+
+        self.assertEqual(thread.last_post_id, post.id)
+        self.assertEqual(thread.last_poster_id, self.user.id)
+        self.assertEqual(thread.last_poster_name, self.user.username)
+        self.assertEqual(thread.last_poster_slug, self.user.slug)
+
+        category = Category.objects.get(pk=self.category.pk)
+        self.assertEqual(category.last_thread_id, thread.id)
+        self.assertEqual(category.last_thread_title, thread.title)
+        self.assertEqual(category.last_thread_slug, thread.slug)
+
+        self.assertEqual(category.last_poster_id, self.user.id)
+        self.assertEqual(category.last_poster_name, self.user.username)
+        self.assertEqual(category.last_poster_slug, self.user.slug)
+
+    def test_post_unicode(self):
+        """unicode characters can be posted"""
+        self.override_acl()
+
+        response = self.client.post(self.api_link, data={
+            'post': "Chrzążczyżewoszyce, powiat Łękółody."
+        })
+        self.assertEqual(response.status_code, 200)

+ 25 - 26
misago/threads/tests/test_thread_start_api.py

@@ -20,7 +20,6 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
 
-        self.root = Category.objects.get(tree_id=threads_tree_id, level=0)
         self.category = Category.objects.get(slug='first-category')
 
         self.api_link = '/api/threads/'
@@ -126,7 +125,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 "You have to enter thread title."
             ],
             'post': [
-                "You have to enter a message"
+                "You have to enter a message."
             ]
         })
 
@@ -136,8 +135,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': '------',
-            'post': 'Lorem ipsum dolor met, sit amet elit!',
+            'title': "------",
+            'post': "Lorem ipsum dolor met, sit amet elit!",
         })
 
         self.assertEqual(response.status_code, 400)
@@ -153,8 +152,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Lorem ipsum dolor met',
-            'post': 'a',
+            'title': "Lorem ipsum dolor met",
+            'post': "a",
         })
 
         self.assertEqual(response.status_code, 400)
@@ -221,8 +220,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'close': True
         })
         self.assertEqual(response.status_code, 200)
@@ -236,8 +235,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'close': True
         })
         self.assertEqual(response.status_code, 200)
@@ -251,8 +250,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'pin': 0
         })
         self.assertEqual(response.status_code, 200)
@@ -266,8 +265,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'pin': 1
         })
         self.assertEqual(response.status_code, 200)
@@ -281,8 +280,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'pin': 2
         })
         self.assertEqual(response.status_code, 200)
@@ -296,8 +295,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'pin': 2
         })
         self.assertEqual(response.status_code, 200)
@@ -311,8 +310,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'pin': 1
         })
         self.assertEqual(response.status_code, 200)
@@ -326,8 +325,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'hide': 1
         })
         self.assertEqual(response.status_code, 200)
@@ -344,8 +343,8 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Hello, I am test thread!',
-            'post': 'Lorem ipsum dolor met!',
+            'title': "Hello, I am test thread!",
+            'post': "Lorem ipsum dolor met!",
             'hide': 1
         })
         self.assertEqual(response.status_code, 200)
@@ -359,7 +358,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link, data={
             'category': self.category.pk,
-            'title': 'Brzęczyżczykiewicz',
-            'post': 'Chrzążczyżewoszyce, powiat Łękółody.'
+            'title': "Brzęczyżczykiewicz",
+            'post': "Chrzążczyżewoszyce, powiat Łękółody."
         })
         self.assertEqual(response.status_code, 200)