Browse Source

private threads list api

Rafał Pitoń 8 years ago
parent
commit
878c18a02e

+ 3 - 6
misago/threads/api/threadendpoints/list.py

@@ -7,12 +7,9 @@ from ...viewmodels.category import (
 from ...viewmodels.threads import ForumThreads, PrivateThreads
 
 
-class ListEndpointBase(object):
-    category = None
+class ThreadsList(object):
     threads = None
 
-    template_name = None
-
     def __call__(self, request, **kwargs):
         page = get_int_or_404(request.query_params.get('page', 0))
         if page == 1:
@@ -32,7 +29,7 @@ class ListEndpointBase(object):
         return threads.get_frontend_context()
 
 
-class ForumThreadsList(ListEndpointBase):
+class ForumThreadsList(ThreadsList):
     threads = ForumThreads
 
     def get_category(self, request, pk=None):
@@ -42,7 +39,7 @@ class ForumThreadsList(ListEndpointBase):
             return ThreadsRootCategory(request)
 
 
-class PrivateThreadsList(ListEndpointBase):
+class PrivateThreadsList(ThreadsList):
     threads = PrivateThreads
 
     def get_category(self, request, pk=None):

+ 10 - 5
misago/threads/models/threadparticipant.py

@@ -8,21 +8,26 @@ class ThreadParticipantManager(models.Manager):
         ThreadParticipant.objects.filter(thread=thread, user=user).delete()
 
     def set_owner(self, thread, user):
-        thread_owner = ThreadParticipant.objects.filter(
-            thread=thread, is_owner=True)
-        thread_owner.update(is_owner=False)
+        # remove existing owner
+        ThreadParticipant.objects.filter(
+            thread=thread,
+            is_owner=True
+        ).update(is_owner=False)
 
+        # add (or re-add) user as thread owner
         self.remove_participant(thread, user)
         ThreadParticipant.objects.create(
             thread=thread,
             user=user,
-            is_owner=True)
+            is_owner=True
+        )
 
     def add_participant(self, thread, user, is_owner=False):
         ThreadParticipant.objects.create(
             thread=thread,
             user=user,
-            is_owner=is_owner)
+            is_owner=is_owner
+        )
 
 
 class ThreadParticipant(models.Model):

+ 4 - 1
misago/threads/permissions/privatethreads.py

@@ -90,8 +90,11 @@ def build_acl(acl, roles, key_name):
 
     private_category = Category.objects.private_threads()
 
+    new_acl['visible_categories'].append(private_category.pk)
+    new_acl['browseable_categories'].append(private_category.pk)
+
     if new_acl['can_moderate_private_threads']:
-        new_acl['can_approve_content'].append(private_category.pk)
+        new_acl['can_see_reports'].append(private_category.pk)
 
     category_acl = {
         'can_see': 1,

+ 2 - 4
misago/threads/permissions/threads.py

@@ -214,18 +214,16 @@ def build_acl(acl, roles, key_name):
     categories_roles = get_categories_roles(roles)
     categories = list(Category.objects.all_categories(include_root=True))
 
-    approve_in_categories = []
-
     for category in categories:
         category_acl = acl['categories'].get(category.pk, {'can_browse': 0})
         if category_acl['can_browse']:
             category_acl = acl['categories'][category.pk] = build_category_acl(
                 category_acl, category, categories_roles, key_name)
 
+            if category_acl.get('can_approve_content'):
+                acl['can_approve_content'].append(category.pk)
             if category_acl.get('can_see_reports'):
                 acl['can_see_reports'].append(category.pk)
-            if category_acl.get('can_approve_content'):
-                approve_in_categories.append(category)
 
     return acl
 

+ 40 - 0
misago/threads/tests/test_privatethreads_api.py

@@ -0,0 +1,40 @@
+from misago.acl.testutils import override_acl
+from misago.categories.models import Category
+from misago.users.testutils import AuthenticatedUserTestCase
+
+
+class PrivateThreadsApiTestCase(AuthenticatedUserTestCase):
+    def setUp(self):
+        super(PrivateThreadsApiTestCase, self).setUp()
+
+        self.category = Category.objects.private_threads()
+
+        override_acl(self.user, {
+            'can_use_private_threads': 1,
+            'can_start_private_threads': 1
+        })
+        self.override_acl()
+
+    def override_acl(self, acl=None):
+        final_acl = self.user.acl['categories'][self.category.pk]
+        final_acl.update({
+            'can_see': 1,
+            'can_browse': 1,
+            'can_see_all_threads': 1,
+            'can_see_own_threads': 0,
+            'can_hide_threads': 0,
+            'can_approve_content': 0,
+            'can_edit_posts': 0,
+            'can_hide_posts': 0,
+            'can_hide_own_posts': 0,
+            'can_merge_threads': 0
+        })
+
+        if acl:
+            final_acl.update(acl)
+
+        override_acl(self.user, {
+            'categories': {
+                self.category.pk: final_acl
+            }
+        })

+ 69 - 0
misago/threads/tests/test_privatethreadslists.py

@@ -0,0 +1,69 @@
+from django.core.urlresolvers import reverse
+
+from misago.acl.testutils import override_acl
+
+from .. import testutils
+from ..participants import add_owner
+from .test_privatethreads_api import PrivateThreadsApiTestCase
+
+
+class PrivateThreadsApiTests(PrivateThreadsApiTestCase):
+    def setUp(self):
+        super(PrivateThreadsApiTests, self).setUp()
+
+        self.api_link = reverse('misago:api:private-thread-list')
+
+    def test_unauthenticated(self):
+        """api requires user to sign in and be able to access it"""
+        self.logout_user()
+
+        response = self.client.get(self.api_link)
+        self.assertContains(response, "sign in to use private threads", status_code=403)
+
+    def test_no_permission(self):
+        """api requires user to have permission to be able to access it"""
+        override_acl(self.user, {
+            'can_use_private_threads': 0
+        })
+
+        response = self.client.get(self.api_link)
+        self.assertContains(response, "can't use private threads", status_code=403)
+
+    def test_empty_list(self):
+        """api has no showstoppers on returning empty list"""
+        response = self.client.get(self.api_link)
+        self.assertEqual(response.status_code, 200)
+
+        response_json = response.json()
+        self.assertEqual(response_json['count'], 0)
+
+    def test_thread_visibility(self):
+        """only participated threads are returned by private threads api"""
+        visible = testutils.post_thread(category=self.category, poster=self.user)
+        hidden = testutils.post_thread(category=self.category, poster=self.user)
+        reported = testutils.post_thread(category=self.category, poster=self.user)
+
+        add_owner(visible, self.user)
+
+        reported.has_reported_posts = True
+        reported.save()
+
+        response = self.client.get(self.api_link)
+        self.assertEqual(response.status_code, 200)
+
+        response_json = response.json()
+        self.assertEqual(response_json['count'], 1)
+        self.assertEqual(response_json['results'][0]['id'], visible.id)
+
+        # threads with reported posts will also show to moderators
+        override_acl(self.user, {
+            'can_moderate_private_threads': 1
+        })
+
+        response = self.client.get(self.api_link)
+        self.assertEqual(response.status_code, 200)
+
+        response_json = response.json()
+        self.assertEqual(response_json['count'], 2)
+        self.assertEqual(response_json['results'][0]['id'], reported.id)
+        self.assertEqual(response_json['results'][1]['id'], visible.id)

+ 2 - 1
misago/threads/tests/test_threadslists.py

@@ -2,6 +2,7 @@ from datetime import timedelta
 from json import loads as json_loads
 
 from django.conf import settings
+from django.core.urlresolvers import reverse
 from django.utils import timezone
 from django.utils.encoding import smart_str
 from django.utils.six.moves import range
@@ -29,7 +30,7 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
     def setUp(self):
         super(ThreadsListTestCase, self).setUp()
 
-        self.api_link = '/api/threads/'
+        self.api_link = reverse('misago:api:thread-list')
 
         self.root = Category.objects.root_category()
         self.first_category = Category.objects.get(slug='first-category')

+ 72 - 0
misago/threads/threadtypes/__init__.py

@@ -7,3 +7,75 @@ class ThreadType(object):
 
     def get_forum_name(self, category):
         return category.name
+
+    def get_category_absolute_url(self, category):
+        return None
+
+    def get_category_last_thread_url(self, category):
+        return None
+
+    def get_category_last_post_url(self, category):
+        return None
+
+    def get_category_read_api_url(self, category):
+        return None
+
+    def get_thread_absolute_url(self, thread, page=1):
+        return None
+
+    def get_thread_last_post_url(self, thread):
+        return None
+
+    def get_thread_new_post_url(self, thread):
+        return None
+
+    def get_thread_unapproved_post_url(self, thread):
+        return None
+
+    def get_thread_api_url(self, thread):
+        return None
+
+    def get_thread_editor_api_url(self, thread):
+        return None
+
+    def get_thread_merge_api_url(self, thread):
+        return None
+
+    def get_thread_poll_api_url(self, thread):
+        return None
+
+    def get_thread_posts_api_url(self, thread):
+        return None
+
+    def get_poll_api_url(self, poll):
+        return None
+
+    def get_poll_votes_api_url(self, poll):
+        return None
+
+    def get_post_merge_api_url(self, thread):
+        return None
+
+    def get_post_move_api_url(self, thread):
+        return None
+
+    def get_post_split_api_url(self, thread):
+        return None
+
+    def get_post_absolute_url(self, post):
+        return None
+
+    def get_post_api_url(self, post):
+        return None
+
+    def get_post_likes_api_url(self, post):
+        return None
+
+    def get_post_editor_api_url(self, post):
+        return None
+
+    def get_post_edits_api_url(self, post):
+        return None
+
+    def get_post_read_api_url(self, post):
+        return None

+ 92 - 0
misago/threads/threadtypes/privatethread.py

@@ -13,6 +13,23 @@ class PrivateThread(ThreadType):
     def get_category_absolute_url(self, category):
         return reverse('misago:private-threads')
 
+    def get_category_last_thread_url(self, category):
+        return reverse('misago:private-thread', kwargs={
+            'slug': category.last_thread_slug,
+            'pk': category.last_thread_id
+        })
+
+    def get_category_last_post_url(self, category):
+        return reverse('misago:private-thread-last', kwargs={
+            'slug': category.last_thread_slug,
+            'pk': category.last_thread_id
+        })
+
+    def get_category_read_api_url(self, category):
+        return reverse('misago:api:category-read', kwargs={
+            'pk': category.pk
+        })
+
     def get_thread_absolute_url(self, thread, page=1):
         if page > 1:
             return reverse('misago:private-thread', kwargs={
@@ -25,3 +42,78 @@ class PrivateThread(ThreadType):
                 'slug': thread.slug,
                 'pk': thread.pk
             })
+
+    def get_thread_last_post_url(self, thread):
+        return reverse('misago:private-thread-last', kwargs={
+            'slug': thread.slug,
+            'pk': thread.pk
+        })
+
+    def get_thread_new_post_url(self, thread):
+        return reverse('misago:private-thread-new', kwargs={
+            'slug': thread.slug,
+            'pk': thread.pk
+        })
+
+    def get_thread_unapproved_post_url(self, thread):
+        return reverse('misago:private-thread-unapproved', kwargs={
+            'slug': thread.slug,
+            'pk': thread.pk
+        })
+
+    def get_thread_api_url(self, thread):
+        return reverse('misago:api:private-thread-detail', kwargs={
+            'pk': thread.pk
+        })
+
+    def get_thread_editor_api_url(self, thread):
+        return reverse('misago:api:private-thread-post-editor', kwargs={
+            'thread_pk': thread.pk
+        })
+
+    def get_thread_posts_api_url(self, thread):
+        return reverse('misago:api:private-thread-post-list', kwargs={
+            'thread_pk': thread.pk
+        })
+
+    def get_post_merge_api_url(self, thread):
+        return reverse('misago:api:private-thread-post-merge', kwargs={
+            'thread_pk': thread.pk
+        })
+
+    def get_post_absolute_url(self, post):
+        return reverse('misago:private-thread-post', kwargs={
+            'slug': post.thread.slug,
+            'pk': post.thread.pk,
+            'post': post.pk
+        })
+
+    def get_post_api_url(self, post):
+        return reverse('misago:api:private-thread-post-detail', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })
+
+    def get_post_likes_api_url(self, post):
+        return reverse('misago:api:private-thread-post-likes', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })
+
+    def get_post_editor_api_url(self, post):
+        return reverse('misago:api:private-thread-post-editor', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })
+
+    def get_post_edits_api_url(self, post):
+        return reverse('misago:api:private-thread-post-edits', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })
+
+    def get_post_read_api_url(self, post):
+        return reverse('misago:api:private-thread-post-read', kwargs={
+            'thread_pk': post.thread_id,
+            'pk': post.pk
+        })

+ 8 - 8
misago/threads/threadtypes/thread.py

@@ -108,26 +108,26 @@ class Thread(ThreadType):
         })
 
     def get_post_merge_api_url(self, thread):
-        reverse('misago:api:thread-post-merge', kwargs={
+        return reverse('misago:api:thread-post-merge', kwargs={
             'thread_pk': thread.pk
         })
 
     def get_post_move_api_url(self, thread):
-        reverse('misago:api:thread-post-move', kwargs={
+        return reverse('misago:api:thread-post-move', kwargs={
             'thread_pk': thread.pk
         })
 
     def get_post_split_api_url(self, thread):
-        reverse('misago:api:thread-post-split', kwargs={
+        return reverse('misago:api:thread-post-split', kwargs={
             'thread_pk': thread.pk
         })
 
     def get_post_absolute_url(self, post):
-            return reverse('misago:thread-post', kwargs={
-                'slug': post.thread.slug,
-                'pk': post.thread.pk,
-                'post': post.pk
-            })
+        return reverse('misago:thread-post', kwargs={
+            'slug': post.thread.slug,
+            'pk': post.thread.pk,
+            'post': post.pk
+        })
 
     def get_post_api_url(self, post):
         return reverse('misago:api:thread-post-detail', kwargs={

+ 8 - 0
misago/threads/urls/__init__.py

@@ -111,6 +111,14 @@ urlpatterns += goto_patterns(
     unapproved=ThreadGotoUnapprovedView
 )
 
+urlpatterns += goto_patterns(
+    'private-thread',
+    post=ThreadGotoPostView,
+    last=ThreadGotoLastView,
+    new=ThreadGotoNewView,
+    unapproved=ThreadGotoUnapprovedView
+)
+
 
 urlpatterns += [
     url(r'^attachment/(?P<secret>[-a-zA-Z0-9]+)-(?P<pk>\d+)/', attachment_server, name='attachment'),

+ 1 - 1
misago/threads/urls/api.py

@@ -15,6 +15,6 @@ router.register(r'threads/(?P<thread_pk>[^/.]+)/posts', ThreadPostsViewSet, base
 router.register(r'threads/(?P<thread_pk>[^/.]+)/poll', ThreadPollViewSet, base_name='thread-poll')
 
 router.register(r'private-threads', PrivateThreadViewSet, base_name='private-thread')
-router.register(r'private-threads/(?P<thread_pk>[^/.]+)/posts', PrivateThreadViewSet, base_name='private-thread-post')
+router.register(r'private-threads/(?P<thread_pk>[^/.]+)/posts', PrivateThreadPostsViewSet, base_name='private-thread-post')
 
 urlpatterns = router.urls

+ 1 - 0
misago/threads/viewmodels/category.py

@@ -17,6 +17,7 @@ class ViewModel(BaseViewModel):
         add_acl(request.user, self._categories)
 
         self._model = self.get_category(request, self._categories, **kwargs)
+
         self._subcategories = list(filter(self._model.has_child, self._categories))
         self._children = list(filter(lambda s: s.parent_id == self._model.pk, self._subcategories))
 

+ 14 - 1
misago/threads/viewmodels/threads.py

@@ -145,11 +145,24 @@ class ForumThreads(ViewModel):
 
 
 class PrivateThreads(ViewModel):
+    def get_base_queryset(self, request, threads_categories, list_type):
+        queryset = super(PrivateThreads, self).get_base_queryset(request, threads_categories, list_type)
+
+        # limit queryset to threads we are participant of
+        participated_threads = request.user.threadparticipant_set.values('thread_id')
+
+        if request.user.acl['can_moderate_private_threads']:
+            queryset = queryset.filter(
+                Q(id__in=participated_threads) | Q(has_reported_posts=True))
+        else:
+            queryset = queryset.filter(id__in=participated_threads)
+
+        return queryset
+
     def get_pinned_threads(self, queryset, category, threads_categories):
         return [] # this is noop for Private Threads where its impossible to weight threads
 
     def get_remaining_threads_queryset(self, queryset, category, threads_categories):
-        # todo: return all with reports (if moderator) or ones user is participating in otherwhise
         return queryset.filter(category__in=threads_categories)