rafalp 6 лет назад
Родитель
Сommit
19d77eaeea

+ 6 - 1
misago/threads/api/postingendpoint/__init__.py

@@ -26,7 +26,12 @@ class PostingEndpoint(object):
 
 
         # build kwargs dict for passing to middlewares
         # build kwargs dict for passing to middlewares
         self.kwargs = kwargs
         self.kwargs = kwargs
-        self.kwargs.update({'mode': mode, 'request': request, 'user': request.user})
+        self.kwargs.update({
+            'mode': mode,
+            'request': request,
+            'user': request.user,
+            'user_acl': request.user_acl,
+        })
 
 
         self.__dict__.update(kwargs)
         self.__dict__.update(kwargs)
 
 

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

@@ -23,12 +23,12 @@ class CategoryMiddleware(PostingMiddleware):
         return False
         return False
 
 
     def get_serializer(self):
     def get_serializer(self):
-        return CategorySerializer(self.user, data=self.request.data)
+        return CategorySerializer(self.user_acl, data=self.request.data)
 
 
     def pre_save(self, serializer):
     def pre_save(self, serializer):
         category = serializer.category_cache
         category = serializer.category_cache
 
 
-        add_acl(self.user, category)
+        add_acl(self.user_acl, category)
 
 
         # set flags for savechanges middleware
         # set flags for savechanges middleware
         category.update_all = False
         category.update_all = False
@@ -47,8 +47,8 @@ class CategorySerializer(serializers.Serializer):
         }
         }
     )
     )
 
 
-    def __init__(self, user, *args, **kwargs):
-        self.user = user
+    def __init__(self, user_acl, *args, **kwargs):
+        self.user_acl = user_acl
         self.category_cache = None
         self.category_cache = None
 
 
         super().__init__(*args, **kwargs)
         super().__init__(*args, **kwargs)
@@ -59,15 +59,15 @@ class CategorySerializer(serializers.Serializer):
                 pk=value, tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
                 pk=value, tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
             )
             )
 
 
-            can_see = can_see_category(self.user, self.category_cache)
-            can_browse = can_browse_category(self.user, self.category_cache)
+            can_see = can_see_category(self.user_acl, self.category_cache)
+            can_browse = can_browse_category(self.user_acl, self.category_cache)
             if not (self.category_cache.level and can_see and can_browse):
             if not (self.category_cache.level and can_see and can_browse):
                 raise PermissionDenied(_("Selected category is invalid."))
                 raise PermissionDenied(_("Selected category is invalid."))
 
 
-            allow_start_thread(self.user, self.category_cache)
-        except PermissionDenied as e:
-            raise serializers.ValidationError(e.args[0])
+            allow_start_thread(self.user_acl, self.category_cache)
         except Category.DoesNotExist:
         except Category.DoesNotExist:
             raise serializers.ValidationError(
             raise serializers.ValidationError(
                 _("Selected category doesn't exist or you don't have permission to browse it.")
                 _("Selected category doesn't exist or you don't have permission to browse it.")
             )
             )
+        except PermissionDenied as e:
+            raise serializers.ValidationError(e.args[0])

+ 3 - 5
misago/threads/api/threadendpoints/merge.py

@@ -15,7 +15,7 @@ from misago.threads.serializers import (
 
 
 
 
 def thread_merge_endpoint(request, thread, viewmodel):
 def thread_merge_endpoint(request, thread, viewmodel):
-    allow_merge_thread(request.user, thread)
+    allow_merge_thread(request.user_acl, thread)
 
 
     serializer = MergeThreadSerializer(
     serializer = MergeThreadSerializer(
         data=request.data,
         data=request.data,
@@ -88,9 +88,7 @@ def thread_merge_endpoint(request, thread, viewmodel):
 def threads_merge_endpoint(request):
 def threads_merge_endpoint(request):
     serializer = MergeThreadsSerializer(
     serializer = MergeThreadsSerializer(
         data=request.data,
         data=request.data,
-        context={
-            'user': request.user
-        },
+        context={'user_acl': request.user_acl},
     )
     )
 
 
     if not serializer.is_valid():
     if not serializer.is_valid():
@@ -108,7 +106,7 @@ def threads_merge_endpoint(request):
 
 
     for thread in threads:
     for thread in threads:
         try:
         try:
-            allow_merge_thread(request.user, thread)
+            allow_merge_thread(request.user_acl, thread)
         except PermissionDenied as e:
         except PermissionDenied as e:
             invalid_threads.append({
             invalid_threads.append({
                 'id': thread.pk,
                 'id': thread.pk,

+ 24 - 23
misago/threads/serializers/moderation.py

@@ -62,10 +62,10 @@ class DeletePostsSerializer(serializers.Serializer):
             )
             )
             raise ValidationError(message % {'limit': POSTS_LIMIT})
             raise ValidationError(message % {'limit': POSTS_LIMIT})
 
 
-        user = self.context['user']
+        user_acl = self.context['user_acl']
         thread = self.context['thread']
         thread = self.context['thread']
 
 
-        posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
+        posts_queryset = exclude_invisible_posts(user_acl, thread.category, thread.post_set)
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
 
 
         posts = []
         posts = []
@@ -74,10 +74,10 @@ class DeletePostsSerializer(serializers.Serializer):
             post.thread = thread
             post.thread = thread
 
 
             if post.is_event:
             if post.is_event:
-                allow_delete_event(user, post)
+                allow_delete_event(user_acl, post)
             else:
             else:
-                allow_delete_best_answer(user, post)
-                allow_delete_post(user, post)
+                allow_delete_best_answer(user_acl, post)
+                allow_delete_post(user_acl, post)
 
 
             posts.append(post)
             posts.append(post)
 
 
@@ -115,10 +115,10 @@ class MergePostsSerializer(serializers.Serializer):
             )
             )
             raise serializers.ValidationError(message % {'limit': POSTS_LIMIT})
             raise serializers.ValidationError(message % {'limit': POSTS_LIMIT})
 
 
-        user = self.context['user']
+        user_acl = self.context['user_acl']
         thread = self.context['thread']
         thread = self.context['thread']
 
 
-        posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
+        posts_queryset = exclude_invisible_posts(user_acl, thread.category, thread.post_set)
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
 
 
         posts = []
         posts = []
@@ -127,7 +127,7 @@ class MergePostsSerializer(serializers.Serializer):
             post.thread = thread
             post.thread = thread
 
 
             try:
             try:
-                allow_merge_post(user, post)
+                allow_merge_post(user_acl, post)
             except PermissionDenied as e:
             except PermissionDenied as e:
                 raise serializers.ValidationError(e)
                 raise serializers.ValidationError(e)
 
 
@@ -232,7 +232,7 @@ class MovePostsSerializer(serializers.Serializer):
             post.thread = thread
             post.thread = thread
 
 
             try:
             try:
-                allow_move_post(request.user, post)
+                allow_move_post(request.user_acl, post)
                 posts.append(post)
                 posts.append(post)
             except PermissionDenied as e:
             except PermissionDenied as e:
                 raise serializers.ValidationError(e)
                 raise serializers.ValidationError(e)
@@ -259,14 +259,15 @@ class NewThreadSerializer(serializers.Serializer):
         return validate_title(title)
         return validate_title(title)
 
 
     def validate_category(self, category_id):
     def validate_category(self, category_id):
-        self.category = validate_category(self.context['user'], category_id)
-        if not can_start_thread(self.context['user'], self.category):
+        user_acl = self.context['user_acl']
+        self.category = validate_category(user_acl, category_id)
+        if not can_start_thread(user_acl, self.category):
             raise ValidationError(_("You can't create new threads in selected category."))
             raise ValidationError(_("You can't create new threads in selected category."))
         return self.category
         return self.category
 
 
     def validate_weight(self, weight):
     def validate_weight(self, weight):
         try:
         try:
-            add_acl(self.context['user'], self.category)
+            add_acl(self.context['user_acl'], self.category)
         except AttributeError:
         except AttributeError:
             return weight  # don't validate weight further if category failed
             return weight  # don't validate weight further if category failed
 
 
@@ -283,7 +284,7 @@ class NewThreadSerializer(serializers.Serializer):
 
 
     def validate_is_hidden(self, is_hidden):
     def validate_is_hidden(self, is_hidden):
         try:
         try:
-            add_acl(self.context['user'], self.category)
+            add_acl(self.context['user_acl'], self.category)
         except AttributeError:
         except AttributeError:
             return is_hidden  # don't validate hidden further if category failed
             return is_hidden  # don't validate hidden further if category failed
 
 
@@ -293,7 +294,7 @@ class NewThreadSerializer(serializers.Serializer):
 
 
     def validate_is_closed(self, is_closed):
     def validate_is_closed(self, is_closed):
         try:
         try:
-            add_acl(self.context['user'], self.category)
+            add_acl(self.context['user_acl'], self.category)
         except AttributeError:
         except AttributeError:
             return is_closed  # don't validate closed further if category failed
             return is_closed  # don't validate closed further if category failed
 
 
@@ -331,9 +332,9 @@ class SplitPostsSerializer(NewThreadSerializer):
             raise ValidationError(message % {'limit': POSTS_LIMIT})
             raise ValidationError(message % {'limit': POSTS_LIMIT})
 
 
         thread = self.context['thread']
         thread = self.context['thread']
-        user = self.context['user']
+        user_acl = self.context['user_acl']
 
 
-        posts_queryset = exclude_invisible_posts(user, thread.category, thread.post_set)
+        posts_queryset = exclude_invisible_posts(user_acl, thread.category, thread.post_set)
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
         posts_queryset = posts_queryset.filter(id__in=data).order_by('id')
 
 
         posts = []
         posts = []
@@ -342,7 +343,7 @@ class SplitPostsSerializer(NewThreadSerializer):
             post.thread = thread
             post.thread = thread
 
 
             try:
             try:
-                allow_split_post(user, post)
+                allow_split_post(user_acl, post)
             except PermissionDenied as e:
             except PermissionDenied as e:
                 raise ValidationError(e)
                 raise ValidationError(e)
 
 
@@ -389,7 +390,7 @@ class DeleteThreadsSerializer(serializers.Serializer):
         for thread_id in data:
         for thread_id in data:
             try:
             try:
                 thread = viewmodel(request, thread_id).unwrap()
                 thread = viewmodel(request, thread_id).unwrap()
-                allow_delete_thread(request.user, thread)
+                allow_delete_thread(request.user_acl, thread)
                 threads.append(thread)
                 threads.append(thread)
             except PermissionDenied as e:
             except PermissionDenied as e:
                 errors.append({
                 errors.append({
@@ -443,7 +444,7 @@ class MergeThreadSerializer(serializers.Serializer):
 
 
         try:
         try:
             other_thread = viewmodel(request, other_thread_id).unwrap()
             other_thread = viewmodel(request, other_thread_id).unwrap()
-            allow_merge_thread(request.user, other_thread, otherthread=True)
+            allow_merge_thread(request.user_acl, other_thread, otherthread=True)
         except PermissionDenied as e:
         except PermissionDenied as e:
             raise serializers.ValidationError(e)
             raise serializers.ValidationError(e)
         except Http404:
         except Http404:
@@ -454,7 +455,7 @@ class MergeThreadSerializer(serializers.Serializer):
                 )
                 )
             )
             )
 
 
-        if not can_reply_thread(request.user, other_thread):
+        if not can_reply_thread(request.user_acl, other_thread):
             raise ValidationError(_("You can't merge this thread into thread you can't reply."))
             raise ValidationError(_("You can't merge this thread into thread you can't reply."))
 
 
         return other_thread
         return other_thread
@@ -518,12 +519,12 @@ class MergeThreadsSerializer(NewThreadSerializer):
             category__tree_id=threads_tree_id,
             category__tree_id=threads_tree_id,
         ).select_related('category').order_by('-id')
         ).select_related('category').order_by('-id')
 
 
-        user = self.context['user']
+        user_acl = self.context['user_acl']
 
 
         threads = []
         threads = []
         for thread in threads_queryset:
         for thread in threads_queryset:
-            add_acl(user, thread)
-            if can_see_thread(user, thread):
+            add_acl(user_acl, thread)
+            if can_see_thread(user_acl, thread):
                 threads.append(thread)
                 threads.append(thread)
 
 
         if len(threads) != len(data):
         if len(threads) != len(data):

+ 2 - 2
misago/threads/test.py

@@ -16,7 +16,7 @@ default_category_acl = {
 }
 }
 
 
 
 
-def patch_category_acl(acl_patch):
+def patch_category_acl(acl_patch=None):
     def patch_acl(_, user_acl):
     def patch_acl(_, user_acl):
         category = Category.objects.get(slug="first-category")
         category = Category.objects.get(slug="first-category")
         category_acl = user_acl['categories'][category.id]
         category_acl = user_acl['categories'][category.id]
@@ -28,7 +28,7 @@ def patch_category_acl(acl_patch):
     return patch_user_acl(patch_acl)
     return patch_user_acl(patch_acl)
 
 
 
 
-def patch_other_category_acl(acl_patch):
+def patch_other_category_acl(acl_patch=None):
     def patch_acl(_, user_acl):
     def patch_acl(_, user_acl):
         src_category = Category.objects.get(slug="first-category")
         src_category = Category.objects.get(slug="first-category")
         category_acl = user_acl['categories'][src_category.id].copy()
         category_acl = user_acl['categories'][src_category.id].copy()

+ 98 - 186
misago/threads/tests/test_thread_merge_api.py

@@ -1,10 +1,10 @@
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.readtracker import poststracker
 from misago.readtracker import poststracker
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.threads.models import Poll, PollVote, Thread
 from misago.threads.models import Poll, PollVote, Thread
+from misago.threads.test import patch_category_acl, patch_other_category_acl
 
 
 from .test_threads_api import ThreadsApiTestCase
 from .test_threads_api import ThreadsApiTestCase
 
 
@@ -12,16 +12,16 @@ from .test_threads_api import ThreadsApiTestCase
 class ThreadMergeApiTests(ThreadsApiTestCase):
 class ThreadMergeApiTests(ThreadsApiTestCase):
     def setUp(self):
     def setUp(self):
         super().setUp()
         super().setUp()
-
+        
         Category(
         Category(
-            name='Category B',
-            slug='category-b',
+            name='Other Category',
+            slug='other-category',
         ).insert_at(
         ).insert_at(
             self.category,
             self.category,
             position='last-child',
             position='last-child',
             save=True,
             save=True,
         )
         )
-        self.category_b = Category.objects.get(slug='category-b')
+        self.other_category = Category.objects.get(slug='other-category')
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-merge', kwargs={
             'misago:api:thread-merge', kwargs={
@@ -29,63 +29,27 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
-    def override_other_acl(self, acl=None):
-        other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
-        other_category_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,
-            'can_close_threads': 0,
-        })
-
-        if acl:
-            other_category_acl.update(acl)
-
-        categories_acl = self.user.acl_cache['categories']
-        categories_acl[self.category_b.pk] = other_category_acl
-
-        visible_categories = [self.category.pk]
-        if other_category_acl['can_see']:
-            visible_categories.append(self.category_b.pk)
-
-        override_acl(
-            self.user, {
-                'visible_categories': visible_categories,
-                'categories': categories_acl,
-            }
-        )
-
+    @patch_category_acl({"can_merge_threads": False})
     def test_merge_no_permission(self):
     def test_merge_no_permission(self):
         """api validates if thread can be merged with other one"""
         """api validates if thread can be merged with other one"""
-        self.override_acl({'can_merge_threads': 0})
-
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.json(), {
         self.assertEqual(response.json(), {
             "detail": "You can't merge threads in this category."
             "detail": "You can't merge threads in this category."
         })
         })
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_no_url(self):
     def test_merge_no_url(self):
         """api validates if thread url was given"""
         """api validates if thread url was given"""
-        self.override_acl({'can_merge_threads': 1})
-
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.json(), {
         self.assertEqual(response.json(), {
             "detail": "Enter link to new thread."
             "detail": "Enter link to new thread."
         })
         })
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_invalid_url(self):
     def test_invalid_url(self):
         """api validates thread url"""
         """api validates thread url"""
-        self.override_acl({'can_merge_threads': 1})
-
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'other_thread': self.user.get_absolute_url(),
             'other_thread': self.user.get_absolute_url(),
         })
         })
@@ -94,10 +58,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "This is not a valid thread link."
             "detail": "This is not a valid thread link."
         })
         })
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_current_other_thread(self):
     def test_current_other_thread(self):
         """api validates if thread url given is to current thread"""
         """api validates if thread url given is to current thread"""
-        self.override_acl({'can_merge_threads': 1})
-
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
                 'other_thread': self.thread.get_absolute_url(),
                 'other_thread': self.thread.get_absolute_url(),
@@ -108,12 +71,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "You can't merge thread with itself."
             "detail": "You can't merge thread with itself."
         })
         })
 
 
+    @patch_other_category_acl()
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_exists(self):
     def test_other_thread_exists(self):
         """api validates if other thread exists"""
         """api validates if other thread exists"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl()
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_other_thread = other_thread.get_absolute_url()
         other_other_thread = other_thread.get_absolute_url()
         other_thread.delete()
         other_thread.delete()
 
 
@@ -128,12 +90,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             )
             )
         })
         })
 
 
+    @patch_other_category_acl({"can_see": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_is_invisible(self):
     def test_other_thread_is_invisible(self):
         """api validates if other thread is visible"""
         """api validates if other thread is visible"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_see': 0})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -148,12 +109,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             )
             )
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_isnt_mergeable(self):
     def test_other_thread_isnt_mergeable(self):
         """api validates if other thread can be merged"""
         """api validates if other thread can be merged"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 0})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -165,17 +125,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "Other thread can't be merged with."
             "detail": "Other thread can't be merged with."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True, "can_close_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_thread_category_is_closed(self):
     def test_thread_category_is_closed(self):
         """api validates if thread's category is open"""
         """api validates if thread's category is open"""
-        self.override_acl({'can_merge_threads': 1})
-
-        self.override_other_acl({
-            'can_merge_threads': 1,
-            'can_reply_threads': 0,
-            'can_close_threads': 0,
-        })
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
@@ -190,17 +144,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "This category is closed. You can't merge it's threads."
             "detail": "This category is closed. You can't merge it's threads."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True, "can_close_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_thread_is_closed(self):
     def test_thread_is_closed(self):
         """api validates if thread is open"""
         """api validates if thread is open"""
-        self.override_acl({'can_merge_threads': 1})
-
-        self.override_other_acl({
-            'can_merge_threads': 1,
-            'can_reply_threads': 0,
-            'can_close_threads': 0,
-        })
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
@@ -215,20 +163,14 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "This thread is closed. You can't merge it with other threads."
             "detail": "This thread is closed. You can't merge it with other threads."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True, "can_close_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_category_is_closed(self):
     def test_other_thread_category_is_closed(self):
         """api validates if other thread's category is open"""
         """api validates if other thread's category is open"""
-        self.override_acl({'can_merge_threads': 1})
-
-        self.override_other_acl({
-            'can_merge_threads': 1,
-            'can_reply_threads': 0,
-            'can_close_threads': 0,
-        })
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
-        self.category_b.is_closed = True
-        self.category_b.save()
+        self.other_category.is_closed = True
+        self.other_category.save()
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -240,17 +182,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "Other thread's category is closed. You can't merge with it."
             "detail": "Other thread's category is closed. You can't merge with it."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True, "can_close_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_is_closed(self):
     def test_other_thread_is_closed(self):
         """api validates if other thread is open"""
         """api validates if other thread is open"""
-        self.override_acl({'can_merge_threads': 1})
-
-        self.override_other_acl({
-            'can_merge_threads': 1,
-            'can_reply_threads': 0,
-            'can_close_threads': 0,
-        })
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         other_thread.is_closed = True
         other_thread.is_closed = True
         other_thread.save()
         other_thread.save()
@@ -265,16 +201,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "Other thread is closed and can't be merged with."
             "detail": "Other thread is closed and can't be merged with."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True, "can_reply_threads": False})
+    @patch_category_acl({"can_merge_threads": True})
     def test_other_thread_isnt_replyable(self):
     def test_other_thread_isnt_replyable(self):
         """api validates if other thread can be replied, which is condition for merge"""
         """api validates if other thread can be replied, which is condition for merge"""
-        self.override_acl({'can_merge_threads': 1})
-
-        self.override_other_acl({
-            'can_merge_threads': 1,
-            'can_reply_threads': 0,
-        })
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -286,12 +217,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             "detail": "You can't merge this thread into thread you can't reply."
             "detail": "You can't merge this thread into thread you can't reply."
         })
         })
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads(self):
     def test_merge_threads(self):
         """api merges two threads successfully"""
         """api merges two threads successfully"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -312,12 +242,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         with self.assertRaises(Thread.DoesNotExist):
         with self.assertRaises(Thread.DoesNotExist):
             Thread.objects.get(pk=self.thread.pk)
             Thread.objects.get(pk=self.thread.pk)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_reads(self):
     def test_merge_threads_kept_reads(self):
         """api keeps both threads readtrackers after merge"""
         """api keeps both threads readtrackers after merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, other_thread.first_post)
         poststracker.save_read(self.user, other_thread.first_post)
@@ -342,14 +271,13 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             [self.thread.first_post_id, other_thread.first_post_id]
             [self.thread.first_post_id, other_thread.first_post_id]
         )
         )
         self.assertEqual(postreads.filter(thread=other_thread).count(), 2)
         self.assertEqual(postreads.filter(thread=other_thread).count(), 2)
-        self.assertEqual(postreads.filter(category=self.category_b).count(), 2)
+        self.assertEqual(postreads.filter(category=self.other_category).count(), 2)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_subs(self):
     def test_merge_threads_kept_subs(self):
         """api keeps other thread's subscription after merge"""
         """api keeps other thread's subscription after merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             thread=self.thread,
             thread=self.thread,
@@ -377,14 +305,13 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         # subscriptions are kept
         # subscriptions are kept
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.user.subscription_set.get(thread=other_thread)
         self.user.subscription_set.get(thread=other_thread)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.other_category)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_subs(self):
     def test_merge_threads_moved_subs(self):
         """api keeps other thread's subscription after merge"""
         """api keeps other thread's subscription after merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             thread=other_thread,
             thread=other_thread,
@@ -395,7 +322,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
 
 
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.user.subscription_set.get(thread=other_thread)
         self.user.subscription_set.get(thread=other_thread)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -412,13 +339,12 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         # subscriptions are kept
         # subscriptions are kept
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.user.subscription_set.get(thread=other_thread)
         self.user.subscription_set.get(thread=other_thread)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.other_category)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_handle_subs_colision(self):
     def test_merge_threads_handle_subs_colision(self):
         """api resolves conflicting thread subscriptions after merge"""
         """api resolves conflicting thread subscriptions after merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             thread=self.thread,
             thread=self.thread,
             category=self.thread.category,
             category=self.thread.category,
@@ -426,7 +352,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             send_email=False,
             send_email=False,
         )
         )
 
 
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             thread=other_thread,
             thread=other_thread,
@@ -439,7 +365,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.user.subscription_set.get(thread=self.thread)
         self.user.subscription_set.get(thread=self.thread)
         self.user.subscription_set.get(category=self.category)
         self.user.subscription_set.get(category=self.category)
         self.user.subscription_set.get(thread=other_thread)
         self.user.subscription_set.get(thread=other_thread)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link, {
             self.api_link, {
@@ -456,14 +382,13 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         # subscriptions are kept
         # subscriptions are kept
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.assertEqual(self.user.subscription_set.count(), 1)
         self.user.subscription_set.get(thread=other_thread)
         self.user.subscription_set.get(thread=other_thread)
-        self.user.subscription_set.get(category=self.category_b)
+        self.user.subscription_set.get(category=self.other_category)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_best_answer(self):
     def test_merge_threads_kept_best_answer(self):
         """api merges two threads successfully, keeping best answer from old thread"""
         """api merges two threads successfully, keeping best answer from old thread"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         best_answer = testutils.reply_thread(other_thread)
         best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, best_answer)
         other_thread.set_best_answer(self.user, best_answer)
         other_thread.save()
         other_thread.save()
@@ -491,12 +416,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_thread = Thread.objects.get(pk=other_thread.pk)
         other_thread = Thread.objects.get(pk=other_thread.pk)
         self.assertEqual(other_thread.best_answer, best_answer)
         self.assertEqual(other_thread.best_answer, best_answer)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_best_answer(self):
     def test_merge_threads_moved_best_answer(self):
         """api merges two threads successfully, moving best answer to old thread"""
         """api merges two threads successfully, moving best answer to old thread"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
@@ -525,16 +449,15 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_thread = Thread.objects.get(pk=other_thread.pk)
         other_thread = Thread.objects.get(pk=other_thread.pk)
         self.assertEqual(other_thread.best_answer, best_answer)
         self.assertEqual(other_thread.best_answer, best_answer)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merge_conflict_best_answer(self):
     def test_merge_threads_merge_conflict_best_answer(self):
         """api errors on merge conflict, returning list of available best answers"""
         """api errors on merge conflict, returning list of available best answers"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
         
         
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_best_answer = testutils.reply_thread(other_thread)
         other_best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
         other_thread.save()
@@ -560,16 +483,15 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(
         self.assertEqual(
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
         """api errors on invalid merge conflict resolution"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
         
         
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_best_answer = testutils.reply_thread(other_thread)
         other_best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
         other_thread.save()
@@ -590,16 +512,15 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(
         self.assertEqual(
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_unmark_all_best_answers(self):
     def test_threads_merge_conflict_unmark_all_best_answers(self):
         """api unmarks all best answers when unmark all choice is selected"""
         """api unmarks all best answers when unmark all choice is selected"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
         
         
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_best_answer = testutils.reply_thread(other_thread)
         other_best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
         other_thread.save()
@@ -627,16 +548,15 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         # final thread has no marked best answer
         # final thread has no marked best answer
         self.assertIsNone(Thread.objects.get(pk=other_thread.pk).best_answer_id)
         self.assertIsNone(Thread.objects.get(pk=other_thread.pk).best_answer_id)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_best_answer(self):
     def test_threads_merge_conflict_keep_first_best_answer(self):
         """api unmarks other best answer on merge"""
         """api unmarks other best answer on merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
         
         
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_best_answer = testutils.reply_thread(other_thread)
         other_best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
         other_thread.save()
@@ -664,16 +584,15 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         # other thread's best answer was unchanged
         # other thread's best answer was unchanged
         self.assertEqual(Thread.objects.get(pk=other_thread.pk).best_answer_id, best_answer.id)
         self.assertEqual(Thread.objects.get(pk=other_thread.pk).best_answer_id, best_answer.id)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_best_answer(self):
     def test_threads_merge_conflict_keep_other_best_answer(self):
         """api unmarks first best answer on merge"""
         """api unmarks first best answer on merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
         
         
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         other_best_answer = testutils.reply_thread(other_thread)
         other_best_answer = testutils.reply_thread(other_thread)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.set_best_answer(self.user, other_best_answer)
         other_thread.save()
         other_thread.save()
@@ -702,12 +621,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(
         self.assertEqual(
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_poll(self):
     def test_merge_threads_kept_poll(self):
         """api merges two threads successfully, keeping poll from other thread"""
         """api merges two threads successfully, keeping poll from other thread"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         poll = testutils.post_poll(other_thread, self.user)
         poll = testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -733,12 +651,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
         self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
         self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
         self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_poll(self):
     def test_merge_threads_moved_poll(self):
         """api merges two threads successfully, moving poll from old thread"""
         """api merges two threads successfully, moving poll from old thread"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -764,12 +681,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
         self.assertEqual(Poll.objects.filter(pk=poll.pk, thread=other_thread).count(), 1)
         self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
         self.assertEqual(PollVote.objects.filter(poll=poll, thread=other_thread).count(), 4)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_polls(self):
     def test_threads_merge_conflict_polls(self):
         """api errors on merge conflict, returning list of available polls"""
         """api errors on merge conflict, returning list of available polls"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 
@@ -799,12 +715,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(PollVote.objects.count(), 8)
         self.assertEqual(PollVote.objects.count(), 8)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll_invalid_resolution(self):
     def test_threads_merge_conflict_poll_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
         """api errors on invalid merge conflict resolution"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
 
 
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(other_thread, self.user)
         testutils.post_poll(other_thread, self.user)
@@ -822,12 +737,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(PollVote.objects.count(), 8)
         self.assertEqual(PollVote.objects.count(), 8)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_delete_all_polls(self):
     def test_threads_merge_conflict_delete_all_polls(self):
         """api deletes all polls when delete all choice is selected"""
         """api deletes all polls when delete all choice is selected"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(other_thread, self.user)
         testutils.post_poll(other_thread, self.user)
 
 
@@ -855,12 +769,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 0)
         self.assertEqual(Poll.objects.count(), 0)
         self.assertEqual(PollVote.objects.count(), 0)
         self.assertEqual(PollVote.objects.count(), 0)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_poll(self):
     def test_threads_merge_conflict_keep_first_poll(self):
         """api deletes other poll on merge"""
         """api deletes other poll on merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 
@@ -895,12 +808,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         with self.assertRaises(Poll.DoesNotExist):
         with self.assertRaises(Poll.DoesNotExist):
             Poll.objects.get(pk=other_poll.pk)
             Poll.objects.get(pk=other_poll.pk)
 
 
+    @patch_other_category_acl({"can_merge_threads": True})
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_poll(self):
     def test_threads_merge_conflict_keep_other_poll(self):
         """api deletes first poll on merge"""
         """api deletes first poll on merge"""
-        self.override_acl({'can_merge_threads': 1})
-        self.override_other_acl({'can_merge_threads': 1})
-
-        other_thread = testutils.post_thread(self.category_b)
+        other_thread = testutils.post_thread(self.other_category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 

+ 57 - 198
misago/threads/tests/test_threads_merge_api.py

@@ -2,17 +2,19 @@ import json
 
 
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl import add_acl
-from misago.acl.testutils import override_acl
+from misago.acl import add_acl, useracl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.readtracker import poststracker
 from misago.readtracker import poststracker
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.serializers.moderation import THREADS_LIMIT
 from misago.threads.models import Poll, PollVote, Post, Thread
 from misago.threads.models import Poll, PollVote, Post, Thread
 from misago.threads.serializers import ThreadsListSerializer
 from misago.threads.serializers import ThreadsListSerializer
+from misago.threads.serializers.moderation import THREADS_LIMIT
+from misago.threads.test import patch_category_acl, patch_other_category_acl
 
 
 from .test_threads_api import ThreadsApiTestCase
 from .test_threads_api import ThreadsApiTestCase
 
 
+cache_versions = {"acl": "abcdefgh"}
+
 
 
 class ThreadsMergeApiTests(ThreadsApiTestCase):
 class ThreadsMergeApiTests(ThreadsApiTestCase):
     def setUp(self):
     def setUp(self):
@@ -20,40 +22,14 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.api_link = reverse('misago:api:thread-merge')
         self.api_link = reverse('misago:api:thread-merge')
 
 
         Category(
         Category(
-            name='Category B',
-            slug='category-b',
+            name='Other Category',
+            slug='other-category',
         ).insert_at(
         ).insert_at(
             self.category,
             self.category,
             position='last-child',
             position='last-child',
             save=True,
             save=True,
         )
         )
-        self.category_b = Category.objects.get(slug='category-b')
-
-    def override_other_category(self):
-        categories =  self.user.acl_cache['categories']
-
-        visible_categories = self.user.acl_cache['visible_categories']
-        browseable_categories = self.user.acl_cache['browseable_categories']
-
-        visible_categories.append(self.category_b.pk)
-        browseable_categories.append(self.category_b.pk)
-
-        override_acl(
-            self.user, {
-                'visible_categories': visible_categories,
-                'browseable_categories': browseable_categories,
-                'categories': {
-                    self.category.pk: categories[self.category.pk],
-                    self.category_b.pk: {
-                        'can_see': 1,
-                        'can_browse': 1,
-                        'can_see_all_threads': 1,
-                        'can_see_own_threads': 0,
-                        'can_start_threads': 2,
-                    },
-                },
-            }
-        )
+        self.other_category = Category.objects.get(slug='other-category')
 
 
     def test_merge_no_threads(self):
     def test_merge_no_threads(self):
         """api validates if we are trying to merge no threads"""
         """api validates if we are trying to merge no threads"""
@@ -143,7 +119,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_merge_with_invisible_thread(self):
     def test_merge_with_invisible_thread(self):
         """api validates if we are trying to merge with inaccesible thread"""
         """api validates if we are trying to merge with inaccesible thread"""
-        unaccesible_thread = testutils.post_thread(category=self.category_b)
+        unaccesible_thread = testutils.post_thread(category=self.other_category)
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
@@ -166,7 +142,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'category': self.category.pk,
+                'category': self.category.id,
                 'title': 'Lorem ipsum dolor',
                 'title': 'Lorem ipsum dolor',
                 'threads': [self.thread.id, thread.id],
                 'threads': [self.thread.id, thread.id],
             }),
             }),
@@ -188,14 +164,10 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             ]
             ]
         )
         )
 
 
+    @patch_other_category_acl()
+    @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_thread_category_is_closed(self):
     def test_thread_category_is_closed(self):
         """api validates if thread's category is open"""
         """api validates if thread's category is open"""
-        self.override_acl({
-            'can_merge_threads': 1,
-            'can_close_threads': 0,
-        })
-        self.override_other_category()
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         self.category.is_closed = True
         self.category.is_closed = True
@@ -204,7 +176,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'category': self.category_b.pk,
+                'category': self.other_category.id,
                 'title': 'Lorem ipsum dolor',
                 'title': 'Lorem ipsum dolor',
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
             }),
             }),
@@ -224,14 +196,10 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             },
             },
         ])
         ])
 
 
+    @patch_other_category_acl()
+    @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_thread_is_closed(self):
     def test_thread_is_closed(self):
         """api validates if thread is open"""
         """api validates if thread is open"""
-        self.override_acl({
-            'can_merge_threads': 1,
-            'can_close_threads': 0,
-        })
-        self.override_other_category()
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         other_thread.is_closed = True
         other_thread.is_closed = True
@@ -240,7 +208,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'category': self.category_b.pk,
+                'category': self.other_category.id,
                 'title': 'Lorem ipsum dolor',
                 'title': 'Lorem ipsum dolor',
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
             }),
             }),
@@ -255,19 +223,13 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             },
             },
         ])
         ])
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_too_many_threads(self):
     def test_merge_too_many_threads(self):
         """api rejects too many threads to merge"""
         """api rejects too many threads to merge"""
         threads = []
         threads = []
         for _ in range(THREADS_LIMIT + 1):
         for _ in range(THREADS_LIMIT + 1):
             threads.append(testutils.post_thread(category=self.category).pk)
             threads.append(testutils.post_thread(category=self.category).pk)
 
 
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
@@ -282,15 +244,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_no_final_thread(self):
     def test_merge_no_final_thread(self):
         """api rejects merge because no data to merge threads was specified"""
         """api rejects merge because no data to merge threads was specified"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -308,15 +264,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_final_title(self):
     def test_merge_invalid_final_title(self):
         """api rejects merge because final thread title was invalid"""
         """api rejects merge because final thread title was invalid"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -335,15 +285,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_category(self):
     def test_merge_invalid_category(self):
         """api rejects merge because final category was invalid"""
         """api rejects merge because final category was invalid"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -351,7 +295,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             json.dumps({
             json.dumps({
                 'threads': [self.thread.id, thread.id],
                 'threads': [self.thread.id, thread.id],
                 'title': 'Valid thread title',
                 'title': 'Valid thread title',
-                'category': self.category_b.id,
+                'category': self.other_category.id,
             }),
             }),
             content_type="application/json",
             content_type="application/json",
         )
         )
@@ -362,16 +306,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_start_threads": False})
     def test_merge_unallowed_start_thread(self):
     def test_merge_unallowed_start_thread(self):
         """api rejects merge because category isn't allowing starting threads"""
         """api rejects merge because category isn't allowing starting threads"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_start_threads': 0,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -390,15 +327,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_invalid_weight(self):
     def test_merge_invalid_weight(self):
         """api rejects merge because final weight was invalid"""
         """api rejects merge because final weight was invalid"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -418,15 +349,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_unallowed_global_weight(self):
     def test_merge_unallowed_global_weight(self):
         """api rejects merge because global weight was unallowed"""
         """api rejects merge because global weight was unallowed"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -446,15 +371,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_unallowed_local_weight(self):
     def test_merge_unallowed_local_weight(self):
         """api rejects merge because local weight was unallowed"""
         """api rejects merge because local weight was unallowed"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -474,16 +393,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_pin_threads": 1})
     def test_merge_allowed_local_weight(self):
     def test_merge_allowed_local_weight(self):
         """api allows local weight"""
         """api allows local weight"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_pin_threads': 1,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -503,16 +415,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_pin_threads": 2})
     def test_merge_allowed_global_weight(self):
     def test_merge_allowed_global_weight(self):
         """api allows global weight"""
         """api allows global weight"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_pin_threads': 2,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -532,15 +437,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_close_threads": False})
     def test_merge_unallowed_close(self):
     def test_merge_unallowed_close(self):
         """api rejects merge because closing thread was unallowed"""
         """api rejects merge because closing thread was unallowed"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -560,15 +459,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_close_threads": True})
     def test_merge_with_close(self):
     def test_merge_with_close(self):
         """api allows for closing thread"""
         """api allows for closing thread"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_close_threads': True,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -589,16 +482,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_hide_threads": 0})
     def test_merge_unallowed_hidden(self):
     def test_merge_unallowed_hidden(self):
         """api rejects merge because hidden thread was unallowed"""
         """api rejects merge because hidden thread was unallowed"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_hide_threads': 0,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -618,16 +504,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True, "can_hide_threads": 1})
     def test_merge_with_hide(self):
     def test_merge_with_hide(self):
         """api allows for hiding thread"""
         """api allows for hiding thread"""
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-            'can_hide_threads': 1,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -648,17 +527,10 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             }
             }
         )
         )
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge(self):
     def test_merge(self):
         """api performs basic merge"""
         """api performs basic merge"""
         posts_ids = [p.id for p in Post.objects.all()]
         posts_ids = [p.id for p in Post.objects.all()]
-
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': False,
-            'can_edit_threads': False,
-            'can_reply_threads': False,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         response = self.client.post(
         response = self.client.post(
@@ -679,8 +551,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         new_thread.is_read = False
         new_thread.is_read = False
         new_thread.subscription = None
         new_thread.subscription = None
 
 
-        add_acl(self.user, new_thread.category)
-        add_acl(self.user, new_thread)
+        user_acl = useracl.get_user_acl(self.user, cache_versions)
+        add_acl(user_acl, new_thread.category)
+        add_acl(user_acl, new_thread)
 
 
         self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
         self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
 
 
@@ -691,17 +564,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         # are old threads gone?
         # are old threads gone?
         self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
         self.assertEqual([t.pk for t in Thread.objects.all()], [new_thread.pk])
 
 
+    @patch_category_acl({
+        "can_merge_threads": True,
+        "can_close_threads": True,
+        "can_hide_threads": 1,
+        "can_pin_threads": 2,
+    })
     def test_merge_kitchensink(self):
     def test_merge_kitchensink(self):
         """api performs merge"""
         """api performs merge"""
         posts_ids = [p.id for p in Post.objects.all()]
         posts_ids = [p.id for p in Post.objects.all()]
-
-        self.override_acl({
-            'can_merge_threads': True,
-            'can_close_threads': True,
-            'can_hide_threads': 1,
-            'can_pin_threads': 2,
-        })
-
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
 
 
         poststracker.save_read(self.user, self.thread.first_post)
         poststracker.save_read(self.user, self.thread.first_post)
@@ -745,8 +616,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertTrue(new_thread.is_closed)
         self.assertTrue(new_thread.is_closed)
         self.assertTrue(new_thread.is_hidden)
         self.assertTrue(new_thread.is_hidden)
 
 
-        add_acl(self.user, new_thread.category)
-        add_acl(self.user, new_thread)
+        user_acl = useracl.get_user_acl(self.user, cache_versions)
+        add_acl(user_acl, new_thread.category)
+        add_acl(user_acl, new_thread)
 
 
         self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
         self.assertEqual(response_json, ThreadsListSerializer(new_thread).data)
 
 
@@ -772,10 +644,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.user.subscription_set.get(thread=new_thread)
         self.user.subscription_set.get(thread=new_thread)
         self.user.subscription_set.get(category=self.category)
         self.user.subscription_set.get(category=self.category)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merged_best_answer(self):
     def test_merge_threads_merged_best_answer(self):
         """api merges two threads successfully, moving best answer to old thread"""
         """api merges two threads successfully, moving best answer to old thread"""
-        self.override_acl({'can_merge_threads': 1})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
@@ -797,10 +668,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         new_thread = Thread.objects.get(pk=response.json()['id'])
         new_thread = Thread.objects.get(pk=response.json()['id'])
         self.assertEqual(new_thread.best_answer_id, best_answer.id)
         self.assertEqual(new_thread.best_answer_id, best_answer.id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_merge_conflict_best_answer(self):
     def test_merge_threads_merge_conflict_best_answer(self):
         """api errors on merge conflict, returning list of available best answers"""
         """api errors on merge conflict, returning list of available best answers"""
-        self.override_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
@@ -835,10 +705,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(
         self.assertEqual(
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
     def test_threads_merge_conflict_best_answer_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
         """api errors on invalid merge conflict resolution"""
-        self.override_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
@@ -868,10 +737,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(
         self.assertEqual(
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
             Thread.objects.get(pk=other_thread.pk).best_answer_id, other_best_answer.id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_unmark_all_best_answers(self):
     def test_threads_merge_conflict_unmark_all_best_answers(self):
         """api unmarks all best answers when unmark all choice is selected"""
         """api unmarks all best answers when unmark all choice is selected"""
-        self.override_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
@@ -898,10 +766,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertFalse(new_thread.has_best_answer)
         self.assertFalse(new_thread.has_best_answer)
         self.assertIsNone(new_thread.best_answer_id)
         self.assertIsNone(new_thread.best_answer_id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_best_answer(self):
     def test_threads_merge_conflict_keep_first_best_answer(self):
         """api unmarks other best answer on merge"""
         """api unmarks other best answer on merge"""
-        self.override_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
@@ -927,10 +794,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         new_thread = Thread.objects.get(pk=response.json()['id'])
         new_thread = Thread.objects.get(pk=response.json()['id'])
         self.assertEqual(new_thread.best_answer_id, best_answer.id)
         self.assertEqual(new_thread.best_answer_id, best_answer.id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_best_answer(self):
     def test_threads_merge_conflict_keep_other_best_answer(self):
         """api unmarks first best answer on merge"""
         """api unmarks first best answer on merge"""
-        self.override_acl({'can_merge_threads': 1})
-
         best_answer = testutils.reply_thread(self.thread)
         best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.set_best_answer(self.user, best_answer)
         self.thread.save()
         self.thread.save()
@@ -956,10 +822,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         new_thread = Thread.objects.get(pk=response.json()['id'])
         new_thread = Thread.objects.get(pk=response.json()['id'])
         self.assertEqual(new_thread.best_answer_id, other_best_answer.id)
         self.assertEqual(new_thread.best_answer_id, other_best_answer.id)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_kept_poll(self):
     def test_merge_threads_kept_poll(self):
         """api merges two threads successfully, keeping poll from other thread"""
         """api merges two threads successfully, keeping poll from other thread"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(other_thread, self.user)
         poll = testutils.post_poll(other_thread, self.user)
 
 
@@ -983,10 +848,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 1)
         self.assertEqual(Poll.objects.count(), 1)
         self.assertEqual(PollVote.objects.count(), 4)
         self.assertEqual(PollVote.objects.count(), 4)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_merge_threads_moved_poll(self):
     def test_merge_threads_moved_poll(self):
         """api merges two threads successfully, moving poll from old thread"""
         """api merges two threads successfully, moving poll from old thread"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
 
 
@@ -1010,10 +874,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 1)
         self.assertEqual(Poll.objects.count(), 1)
         self.assertEqual(PollVote.objects.count(), 4)
         self.assertEqual(PollVote.objects.count(), 4)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll(self):
     def test_threads_merge_conflict_poll(self):
         """api errors on merge conflict, returning list of available polls"""
         """api errors on merge conflict, returning list of available polls"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
@@ -1049,10 +912,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(PollVote.objects.count(), 8)
         self.assertEqual(PollVote.objects.count(), 8)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_poll_invalid_resolution(self):
     def test_threads_merge_conflict_poll_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
         """api errors on invalid merge conflict resolution"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(self.thread, self.user)
@@ -1078,10 +940,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(PollVote.objects.count(), 8)
         self.assertEqual(PollVote.objects.count(), 8)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_delete_all_polls(self):
     def test_threads_merge_conflict_delete_all_polls(self):
         """api deletes all polls when delete all choice is selected"""
         """api deletes all polls when delete all choice is selected"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
         testutils.post_poll(self.thread, self.user)
         testutils.post_poll(self.thread, self.user)
@@ -1103,10 +964,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(Poll.objects.count(), 0)
         self.assertEqual(Poll.objects.count(), 0)
         self.assertEqual(PollVote.objects.count(), 0)
         self.assertEqual(PollVote.objects.count(), 0)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_first_poll(self):
     def test_threads_merge_conflict_keep_first_poll(self):
         """api deletes other poll on merge"""
         """api deletes other poll on merge"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
@@ -1131,10 +991,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         with self.assertRaises(Poll.DoesNotExist):
         with self.assertRaises(Poll.DoesNotExist):
             Poll.objects.get(pk=other_poll.pk)
             Poll.objects.get(pk=other_poll.pk)
 
 
+    @patch_category_acl({"can_merge_threads": True})
     def test_threads_merge_conflict_keep_other_poll(self):
     def test_threads_merge_conflict_keep_other_poll(self):
         """api deletes first poll on merge"""
         """api deletes first poll on merge"""
-        self.override_acl({'can_merge_threads': True})
-
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)

+ 3 - 3
misago/threads/validators.py

@@ -12,7 +12,7 @@ from misago.core.validators import validate_sluggable
 from .threadtypes import trees_map
 from .threadtypes import trees_map
 
 
 
 
-def validate_category(user, category_id, allow_root=False):
+def validate_category(user_acl, category_id, allow_root=False):
     try:
     try:
         threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
         threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
         category = Category.objects.get(
         category = Category.objects.get(
@@ -26,10 +26,10 @@ def validate_category(user, category_id, allow_root=False):
     if allow_root and category and not category.level:
     if allow_root and category and not category.level:
         return category
         return category
 
 
-    if not category or not can_see_category(user, category):
+    if not category or not can_see_category(user_acl, category):
         raise ValidationError(_("Requested category could not be found."))
         raise ValidationError(_("Requested category could not be found."))
 
 
-    if not can_browse_category(user, category):
+    if not can_browse_category(user_acl, category):
         raise ValidationError(_("You don't have permission to access this category."))
         raise ValidationError(_("You don't have permission to access this category."))
     return category
     return category