Browse Source

Make tests pass for misago.users

rafalp 6 years ago
parent
commit
1e2df6e9cf
63 changed files with 556 additions and 538 deletions
  1. 5 5
      misago/acl/api.py
  2. 1 1
      misago/acl/middleware.py
  3. 1 1
      misago/acl/panels.py
  4. 4 1
      misago/acl/test.py
  5. 7 7
      misago/categories/permissions.py
  6. 1 1
      misago/search/api.py
  7. 1 1
      misago/search/context_processors.py
  8. 2 2
      misago/search/views.py
  9. 3 3
      misago/threads/api/attachments.py
  10. 1 1
      misago/threads/api/pollvotecreateendpoint.py
  11. 1 1
      misago/threads/api/postendpoints/edits.py
  12. 1 1
      misago/threads/api/postendpoints/merge.py
  13. 1 1
      misago/threads/api/postendpoints/patch_event.py
  14. 1 1
      misago/threads/api/postendpoints/patch_post.py
  15. 2 2
      misago/threads/api/threadendpoints/editor.py
  16. 1 1
      misago/threads/api/threadendpoints/merge.py
  17. 2 2
      misago/threads/api/threadendpoints/patch.py
  18. 2 2
      misago/threads/api/threadpoll.py
  19. 1 1
      misago/threads/api/threadposts.py
  20. 1 1
      misago/threads/api/threads.py
  21. 1 1
      misago/threads/middleware.py
  22. 4 4
      misago/threads/permissions/attachments.py
  23. 29 29
      misago/threads/permissions/bestanswers.py
  24. 37 37
      misago/threads/permissions/polls.py
  25. 26 24
      misago/threads/permissions/privatethreads.py
  26. 151 151
      misago/threads/permissions/threads.py
  27. 1 1
      misago/threads/search.py
  28. 2 2
      misago/threads/viewmodels/category.py
  29. 1 1
      misago/threads/viewmodels/post.py
  30. 1 1
      misago/threads/viewmodels/posts.py
  31. 6 6
      misago/threads/viewmodels/thread.py
  32. 7 8
      misago/threads/viewmodels/threads.py
  33. 1 1
      misago/threads/views/attachment.py
  34. 3 3
      misago/users/api/userendpoints/signature.py
  35. 1 1
      misago/users/api/usernamechanges.py
  36. 9 9
      misago/users/api/users.py
  37. 2 2
      misago/users/apps.py
  38. 14 4
      misago/users/models/user.py
  39. 2 2
      misago/users/online/utils.py
  40. 13 11
      misago/users/permissions/decorators.py
  41. 7 7
      misago/users/permissions/delete.py
  42. 28 27
      misago/users/permissions/moderation.py
  43. 13 12
      misago/users/permissions/profiles.py
  44. 1 1
      misago/users/profilefields/default.py
  45. 1 1
      misago/users/profilefields/serializers.py
  46. 1 1
      misago/users/search.py
  47. 6 3
      misago/users/serializers/user.py
  48. 7 5
      misago/users/tests/test_joinip_profilefield.py
  49. 5 8
      misago/users/tests/test_lists_views.py
  50. 17 11
      misago/users/tests/test_online_utils.py
  51. 5 7
      misago/users/tests/test_profile_views.py
  52. 4 3
      misago/users/tests/test_search.py
  53. 10 23
      misago/users/tests/test_user_avatar_api.py
  54. 5 5
      misago/users/tests/test_user_details_api.py
  55. 5 5
      misago/users/tests/test_user_editdetails_api.py
  56. 19 13
      misago/users/tests/test_user_signature_api.py
  57. 10 27
      misago/users/tests/test_user_username_api.py
  58. 16 11
      misago/users/tests/test_usernamechanges_api.py
  59. 37 25
      misago/users/tests/test_users_api.py
  60. 1 1
      misago/users/viewmodels/posts.py
  61. 3 3
      misago/users/viewmodels/threads.py
  62. 2 2
      misago/users/views/lists.py
  63. 3 3
      misago/users/views/profile.py

+ 5 - 5
misago/acl/api.py

@@ -38,21 +38,21 @@ def get_user_acl(user):
         return new_acl
         return new_acl
 
 
 
 
-def add_acl(user, target):
+def add_acl(user_acl, target):
     """add valid ACL to target (iterable of objects or single object)"""
     """add valid ACL to target (iterable of objects or single object)"""
     if hasattr(target, '__iter__'):
     if hasattr(target, '__iter__'):
         for item in target:
         for item in target:
-            _add_acl_to_target(user, item)
+            _add_acl_to_target(user_acl, item)
     else:
     else:
-        _add_acl_to_target(user, target)
+        _add_acl_to_target(user_acl, target)
 
 
 
 
-def _add_acl_to_target(user, target):
+def _add_acl_to_target(user_acl, target):
     """add valid ACL to single target, helper for add_acl function"""
     """add valid ACL to single target, helper for add_acl function"""
     target.acl = {}
     target.acl = {}
 
 
     for annotator in providers.get_obj_type_annotators(target):
     for annotator in providers.get_obj_type_annotators(target):
-        annotator(user, target)
+        annotator(user_acl, target)
 
 
 
 
 def serialize_acl(target):
 def serialize_acl(target):

+ 1 - 1
misago/acl/middleware.py

@@ -4,7 +4,7 @@ from . import useracl
 
 
 
 
 def user_acl_middleware(get_response):
 def user_acl_middleware(get_response):
-    """Sets request.cache_versions attribute with dict of cache versions."""
+    """Sets request.user_acl attribute with dict containing current user acl."""
     def middleware(request):
     def middleware(request):
         request.user_acl = useracl.get_user_acl(request.user, request.cache_versions)
         request.user_acl = useracl.get_user_acl(request.user, request.cache_versions)
         return get_response(request)
         return get_response(request)

+ 1 - 1
misago/acl/panels.py

@@ -24,7 +24,7 @@ class MisagoACLPanel(Panel):
             misago_user = None
             misago_user = None
 
 
         try:
         try:
-            misago_acl = misago_user.acl_cache
+            misago_acl = request.user_acl
         except AttributeError:
         except AttributeError:
             misago_acl = {}
             misago_acl = {}
 
 

+ 4 - 1
misago/acl/test.py

@@ -3,6 +3,8 @@ from unittest.mock import patch
 
 
 from .useracl import get_user_acl
 from .useracl import get_user_acl
 
 
+__all__ = ["patch_user_acl"]
+
 
 
 class PatchUserACL:
 class PatchUserACL:
     def patch_user_acl(self, user, patch):
     def patch_user_acl(self, user, patch):
@@ -27,7 +29,8 @@ class PatchUserACL:
                     "misago.acl.useracl.get_user_acl",
                     "misago.acl.useracl.get_user_acl",
                     side_effect=self.patched_get_user_acl,
                     side_effect=self.patched_get_user_acl,
                 ):
                 ):
-                    return f(*args, self.patch_user_acl, **kwargs)
+                    new_args = args + (self.patch_user_acl,)
+                    return f(*new_args, **kwargs)
         
         
         return inner
         return inner
 
 

+ 7 - 7
misago/categories/permissions.py

@@ -85,9 +85,9 @@ def build_category_acl(acl, category, categories_roles, key_name):
             acl['browseable_categories'].append(category.pk)
             acl['browseable_categories'].append(category.pk)
 
 
 
 
-def add_acl_to_category(user, target):
-    target.acl['can_see'] = can_see_category(user, target)
-    target.acl['can_browse'] = can_browse_category(user, target)
+def add_acl_to_category(user_acl, target):
+    target.acl['can_see'] = can_see_category(user_acl, target)
+    target.acl['can_browse'] = can_browse_category(user_acl, target)
 
 
 
 
 def serialize_categories_acls(serialized_acl):
 def serialize_categories_acls(serialized_acl):
@@ -112,21 +112,21 @@ def register_with(registry):
     registry.acl_serializer(AnonymousUser, serialize_categories_acls)
     registry.acl_serializer(AnonymousUser, serialize_categories_acls)
 
 
 
 
-def allow_see_category(user, target):
+def allow_see_category(user_acl, target):
     try:
     try:
         category_id = target.pk
         category_id = target.pk
     except AttributeError:
     except AttributeError:
         category_id = int(target)
         category_id = int(target)
 
 
-    if not category_id in user.acl_cache['visible_categories']:
+    if not category_id in user_acl['visible_categories']:
         raise Http404()
         raise Http404()
 
 
 
 
 can_see_category = return_boolean(allow_see_category)
 can_see_category = return_boolean(allow_see_category)
 
 
 
 
-def allow_browse_category(user, target):
-    target_acl = user.acl_cache['categories'].get(target.id, {'can_browse': False})
+def allow_browse_category(user_acl, target):
+    target_acl = user_acl['categories'].get(target.id, {'can_browse': False})
     if not target_acl['can_browse']:
     if not target_acl['can_browse']:
         message = _('You don\'t have permission to browse "%(category)s" contents.')
         message = _('You don\'t have permission to browse "%(category)s" contents.')
         raise PermissionDenied(message % {'category': target.name})
         raise PermissionDenied(message % {'category': target.name})

+ 1 - 1
misago/search/api.py

@@ -15,7 +15,7 @@ from .searchproviders import searchproviders
 @api_view()
 @api_view()
 def search(request, search_provider=None):
 def search(request, search_provider=None):
     allowed_providers = searchproviders.get_allowed_providers(request)
     allowed_providers = searchproviders.get_allowed_providers(request)
-    if not request.user.acl_cache['can_search'] or not allowed_providers:
+    if not request.user_acl['can_search'] or not allowed_providers:
         raise PermissionDenied(_("You don't have permission to search site."))
         raise PermissionDenied(_("You don't have permission to search site."))
 
 
     search_query = get_search_query(request)
     search_query = get_search_query(request)

+ 1 - 1
misago/search/context_processors.py

@@ -8,7 +8,7 @@ def search_providers(request):
     allowed_providers = []
     allowed_providers = []
 
 
     try:
     try:
-        if request.user.acl_cache['can_search']:
+        if request.user_acl['can_search']:
             allowed_providers = searchproviders.get_allowed_providers(request)
             allowed_providers = searchproviders.get_allowed_providers(request)
     except AttributeError:
     except AttributeError:
         # is user has no acl_cache attribute, cease entire middleware
         # is user has no acl_cache attribute, cease entire middleware

+ 2 - 2
misago/search/views.py

@@ -9,7 +9,7 @@ from .searchproviders import searchproviders
 
 
 def landing(request):
 def landing(request):
     allowed_providers = searchproviders.get_allowed_providers(request)
     allowed_providers = searchproviders.get_allowed_providers(request)
-    if not request.user.acl_cache['can_search'] or not allowed_providers:
+    if not request.user_acl['can_search'] or not allowed_providers:
         raise PermissionDenied(_("You don't have permission to search site."))
         raise PermissionDenied(_("You don't have permission to search site."))
 
 
     default_provider = allowed_providers[0]
     default_provider = allowed_providers[0]
@@ -18,7 +18,7 @@ def landing(request):
 
 
 def search(request, search_provider):
 def search(request, search_provider):
     all_providers = searchproviders.get_providers(request)
     all_providers = searchproviders.get_providers(request)
-    if not request.user.acl_cache['can_search'] or not all_providers:
+    if not request.user_acl['can_search'] or not all_providers:
         raise PermissionDenied(_("You don't have permission to search site."))
         raise PermissionDenied(_("You don't have permission to search site."))
 
 
     for provider in all_providers:
     for provider in all_providers:

+ 3 - 3
misago/threads/api/attachments.py

@@ -16,7 +16,7 @@ IMAGE_EXTENSIONS = ('jpg', 'jpeg', 'png', 'gif')
 
 
 class AttachmentViewSet(viewsets.ViewSet):
 class AttachmentViewSet(viewsets.ViewSet):
     def create(self, request):
     def create(self, request):
-        if not request.user.acl_cache['max_attachment_size']:
+        if not request.user_acl['max_attachment_size']:
             raise PermissionDenied(_("You don't have permission to upload new files."))
             raise PermissionDenied(_("You don't have permission to upload new files."))
 
 
         try:
         try:
@@ -31,7 +31,7 @@ class AttachmentViewSet(viewsets.ViewSet):
 
 
         user_roles = set(r.pk for r in request.user.get_roles())
         user_roles = set(r.pk for r in request.user.get_roles())
         filetype = validate_filetype(upload, user_roles)
         filetype = validate_filetype(upload, user_roles)
-        validate_filesize(upload, filetype, request.user.acl_cache['max_attachment_size'])
+        validate_filesize(upload, filetype, request.user_acl['max_attachment_size'])
 
 
         attachment = Attachment(
         attachment = Attachment(
             secret=Attachment.generate_new_secret(),
             secret=Attachment.generate_new_secret(),
@@ -52,7 +52,7 @@ class AttachmentViewSet(viewsets.ViewSet):
             attachment.set_file(upload)
             attachment.set_file(upload)
 
 
         attachment.save()
         attachment.save()
-        add_acl(request.user, attachment)
+        add_acl(request.user_acl, attachment)
 
 
         create_audit_trail(request, attachment)
         create_audit_trail(request, attachment)
 
 

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

@@ -33,7 +33,7 @@ def poll_vote_create(request, thread, poll):
     remove_user_votes(request.user, poll, serializer.data['choices'])
     remove_user_votes(request.user, poll, serializer.data['choices'])
     set_new_votes(request, poll, serializer.data['choices'])
     set_new_votes(request, poll, serializer.data['choices'])
 
 
-    add_acl(request.user, poll)
+    add_acl(request.user_acl, poll)
     serialized_poll = PollSerializer(poll).data
     serialized_poll = PollSerializer(poll).data
 
 
     poll.choices = list(map(presave_clean_choice, deepcopy(poll.choices)))
     poll.choices = list(map(presave_clean_choice, deepcopy(poll.choices)))

+ 1 - 1
misago/threads/api/postendpoints/edits.py

@@ -71,7 +71,7 @@ def revert_post_endpoint(request, post):
     post.is_new = False
     post.is_new = False
     post.edits = post_edits + 1
     post.edits = post_edits + 1
 
 
-    add_acl(request.user, post)
+    add_acl(request.user_acl, post)
 
 
     if post.poster:
     if post.poster:
         make_users_status_aware(request, [post.poster])
         make_users_status_aware(request, [post.poster])

+ 1 - 1
misago/threads/api/postendpoints/merge.py

@@ -55,6 +55,6 @@ def posts_merge_endpoint(request, thread):
     first_post.thread = thread
     first_post.thread = thread
     first_post.category = thread.category
     first_post.category = thread.category
 
 
-    add_acl(request.user, first_post)
+    add_acl(request.user_acl, first_post)
 
 
     return Response(PostSerializer(first_post, context={'user': request.user}).data)
     return Response(PostSerializer(first_post, context={'user': request.user}).data)

+ 1 - 1
misago/threads/api/postendpoints/patch_event.py

@@ -13,7 +13,7 @@ event_patch_dispatcher = ApiPatch()
 def patch_acl(request, event, value):
 def patch_acl(request, event, value):
     """useful little op that updates event acl to current state"""
     """useful little op that updates event acl to current state"""
     if value:
     if value:
-        add_acl(request.user, event)
+        add_acl(request.user_acl, event)
         return {'acl': event.acl}
         return {'acl': event.acl}
     else:
     else:
         return {'acl': None}
         return {'acl': None}

+ 1 - 1
misago/threads/api/postendpoints/patch_post.py

@@ -23,7 +23,7 @@ post_patch_dispatcher = ApiPatch()
 def patch_acl(request, post, value):
 def patch_acl(request, post, value):
     """useful little op that updates post acl to current state"""
     """useful little op that updates post acl to current state"""
     if value:
     if value:
-        add_acl(request.user, post)
+        add_acl(request.user_acl, post)
         return {'acl': post.acl}
         return {'acl': post.acl}
     else:
     else:
         return {'acl': None}
         return {'acl': None}

+ 2 - 2
misago/threads/api/threadendpoints/editor.py

@@ -19,12 +19,12 @@ def thread_start_editor(request):
     categories = []
     categories = []
 
 
     queryset = Category.objects.filter(
     queryset = Category.objects.filter(
-        pk__in=request.user.acl_cache['browseable_categories'],
+        pk__in=request.user_acl['browseable_categories'],
         tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
         tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
     ).order_by('-lft')
     ).order_by('-lft')
 
 
     for category in queryset:
     for category in queryset:
-        add_acl(request.user, category)
+        add_acl(request.user_acl, category)
 
 
         post = False
         post = False
         if can_start_thread(request.user, category):
         if can_start_thread(request.user, category):

+ 1 - 1
misago/threads/api/threadendpoints/merge.py

@@ -191,5 +191,5 @@ def merge_threads(request, validated_data, threads, merge_conflict):
     new_thread.is_read = False
     new_thread.is_read = False
     new_thread.subscription = None
     new_thread.subscription = None
 
 
-    add_acl(request.user, new_thread)
+    add_acl(request.user_acl, new_thread)
     return new_thread
     return new_thread

+ 2 - 2
misago/threads/api/threadendpoints/patch.py

@@ -37,7 +37,7 @@ thread_patch_dispatcher = ApiPatch()
 def patch_acl(request, thread, value):
 def patch_acl(request, thread, value):
     """useful little op that updates thread acl to current state"""
     """useful little op that updates thread acl to current state"""
     if value:
     if value:
-        add_acl(request.user, thread)
+        add_acl(request.user_acl, thread)
         return {'acl': thread.acl}
         return {'acl': thread.acl}
     else:
     else:
         return {'acl': None}
         return {'acl': None}
@@ -96,7 +96,7 @@ def patch_move(request, thread, value):
         Category.objects.all_categories().select_related('parent'), pk=category_pk
         Category.objects.all_categories().select_related('parent'), pk=category_pk
     )
     )
 
 
-    add_acl(request.user, new_category)
+    add_acl(request.user_acl, new_category)
     allow_see_category(request.user, new_category)
     allow_see_category(request.user, new_category)
     allow_browse_category(request.user, new_category)
     allow_browse_category(request.user, new_category)
     allow_start_thread(request.user, new_category)
     allow_start_thread(request.user, new_category)

+ 2 - 2
misago/threads/api/threadpoll.py

@@ -68,7 +68,7 @@ class ViewSet(viewsets.ViewSet):
 
 
         serializer.save()
         serializer.save()
 
 
-        add_acl(request.user, instance)
+        add_acl(request.user_acl, instance)
         for choice in instance.choices:
         for choice in instance.choices:
             choice['selected'] = False
             choice['selected'] = False
 
 
@@ -91,7 +91,7 @@ class ViewSet(viewsets.ViewSet):
 
 
         serializer.save()
         serializer.save()
 
 
-        add_acl(request.user, instance)
+        add_acl(request.user_acl, instance)
         instance.make_choices_votes_aware(request.user)
         instance.make_choices_votes_aware(request.user)
 
 
         create_audit_trail(request, instance)
         create_audit_trail(request, instance)

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

@@ -192,7 +192,7 @@ class ViewSet(viewsets.ViewSet):
 
 
         attachments = []
         attachments = []
         for attachment in post.attachment_set.order_by('-id'):
         for attachment in post.attachment_set.order_by('-id'):
-            add_acl(request.user, attachment)
+            add_acl(request.user_acl, attachment)
             attachments.append(attachment)
             attachments.append(attachment)
         attachments_json = AttachmentSerializer(
         attachments_json = AttachmentSerializer(
             attachments, many=True, context={'user': request.user}
             attachments, many=True, context={'user': request.user}

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

@@ -119,7 +119,7 @@ class PrivateThreadViewSet(ViewSet):
     @transaction.atomic
     @transaction.atomic
     def create(self, request):
     def create(self, request):
         allow_use_private_threads(request.user)
         allow_use_private_threads(request.user)
-        if not request.user.acl_cache['can_start_private_threads']:
+        if not request.user_acl['can_start_private_threads']:
             raise PermissionDenied(_("You can't start private threads."))
             raise PermissionDenied(_("You can't start private threads."))
 
 
         request.user.lock()
         request.user.lock()

+ 1 - 1
misago/threads/middleware.py

@@ -11,7 +11,7 @@ class UnreadThreadsCountMiddleware(MiddlewareMixin):
         if request.user.is_anonymous:
         if request.user.is_anonymous:
             return
             return
 
 
-        if not request.user.acl_cache['can_use_private_threads']:
+        if not request.user_acl['can_use_private_threads']:
             return
             return
 
 
         if not request.user.sync_unread_private_threads:
         if not request.user.sync_unread_private_threads:

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

@@ -58,15 +58,15 @@ def build_acl(acl, roles, key_name):
     )
     )
 
 
 
 
-def add_acl_to_attachment(user, attachment):
-    if user.is_authenticated and user.id == attachment.uploader_id:
+def add_acl_to_attachment(user_acl, attachment):
+    if user_acl["is_authenticated"] and user_acl["user_id"] == attachment.uploader_id:
         attachment.acl.update({
         attachment.acl.update({
             'can_delete': True,
             'can_delete': True,
         })
         })
     else:
     else:
-        user_can_delete = user.acl_cache['can_delete_other_users_attachments']
+        user_can_delete = user_acl['can_delete_other_users_attachments']
         attachment.acl.update({
         attachment.acl.update({
-            'can_delete': user.is_authenticated and user_can_delete,
+            'can_delete': user_acl["is_authenticated"] and user_can_delete,
         })
         })
 
 
 
 

+ 29 - 29
misago/threads/permissions/bestanswers.py

@@ -108,19 +108,19 @@ def build_category_acl(acl, category, categories_roles, key_name):
     return final_acl
     return final_acl
 
 
 
 
-def add_acl_to_thread(user, thread):
+def add_acl_to_thread(user_acl, thread):
     thread.acl.update({
     thread.acl.update({
-        'can_mark_best_answer': can_mark_best_answer(user, thread),
-        'can_change_best_answer': can_change_best_answer(user, thread),
-        'can_unmark_best_answer': can_unmark_best_answer(user, thread),
+        'can_mark_best_answer': can_mark_best_answer(user_acl, thread),
+        'can_change_best_answer': can_change_best_answer(user_acl, thread),
+        'can_unmark_best_answer': can_unmark_best_answer(user_acl, thread),
     })
     })
     
     
 
 
-def add_acl_to_post(user, post):
+def add_acl_to_post(user_acl, post):
     post.acl.update({
     post.acl.update({
-        'can_mark_as_best_answer': can_mark_as_best_answer(user, post),
-        'can_hide_best_answer': can_hide_best_answer(user, post),
-        'can_delete_best_answer': can_delete_best_answer(user, post),
+        'can_mark_as_best_answer': can_mark_as_best_answer(user_acl, post),
+        'can_hide_best_answer': can_hide_best_answer(user_acl, post),
+        'can_delete_best_answer': can_delete_best_answer(user_acl, post),
     })
     })
 
 
 
 
@@ -129,11 +129,11 @@ def register_with(registry):
     registry.acl_annotator(Post, add_acl_to_post)
     registry.acl_annotator(Post, add_acl_to_post)
 
 
 
 
-def allow_mark_best_answer(user, target):
-    if user.is_anonymous:
+def allow_mark_best_answer(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to mark best answers."))
         raise PermissionDenied(_("You have to sign in to mark best answers."))
 
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {})
+    category_acl = user_acl['categories'].get(target.category_id, {})
 
 
     if not category_acl.get('can_mark_best_answers'):
     if not category_acl.get('can_mark_best_answers'):
         raise PermissionDenied(
         raise PermissionDenied(
@@ -144,7 +144,7 @@ def allow_mark_best_answer(user, target):
             }
             }
         )
         )
 
 
-    if category_acl['can_mark_best_answers'] == 1 and target.starter_id != user.id:
+    if category_acl['can_mark_best_answers'] == 1 and user_acl["user_id"] != target.starter_id:
         raise PermissionDenied(
         raise PermissionDenied(
             _(
             _(
                 "You don't have permission to mark best answer in this thread because you didn't "
                 "You don't have permission to mark best answer in this thread because you didn't "
@@ -174,11 +174,11 @@ def allow_mark_best_answer(user, target):
 can_mark_best_answer = return_boolean(allow_mark_best_answer)
 can_mark_best_answer = return_boolean(allow_mark_best_answer)
 
 
 
 
-def allow_change_best_answer(user, target):
+def allow_change_best_answer(user_acl, target):
     if not target.has_best_answer:
     if not target.has_best_answer:
         return # shortcircut permission test
         return # shortcircut permission test
 
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {})
+    category_acl = user_acl['categories'].get(target.category_id, {})
 
 
     if not category_acl.get('can_change_marked_answers'):
     if not category_acl.get('can_change_marked_answers'):
         raise PermissionDenied(
         raise PermissionDenied(
@@ -191,14 +191,14 @@ def allow_change_best_answer(user, target):
         )
         )
 
 
     if category_acl['can_change_marked_answers'] == 1:
     if category_acl['can_change_marked_answers'] == 1:
-        if target.starter_id != user.id:
+        if user_acl["user_id"] != target.starter_id:
             raise PermissionDenied(
             raise PermissionDenied(
                 _(
                 _(
                     "You don't have permission to change this thread's marked answer because you "
                     "You don't have permission to change this thread's marked answer because you "
                     "are not a thread starter."
                     "are not a thread starter."
                 )
                 )
             )
             )
-        if not has_time_to_change_answer(user, target):
+        if not has_time_to_change_answer(user_acl, target):
             raise PermissionDenied(
             raise PermissionDenied(
                 ngettext(
                 ngettext(
                     (
                     (
@@ -227,14 +227,14 @@ def allow_change_best_answer(user, target):
 can_change_best_answer = return_boolean(allow_change_best_answer)
 can_change_best_answer = return_boolean(allow_change_best_answer)
 
 
 
 
-def allow_unmark_best_answer(user, target):
-    if user.is_anonymous:
+def allow_unmark_best_answer(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to unmark best answers."))
         raise PermissionDenied(_("You have to sign in to unmark best answers."))
 
 
     if not target.has_best_answer:
     if not target.has_best_answer:
         return # shortcircut test
         return # shortcircut test
 
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {})
+    category_acl = user_acl['categories'].get(target.category_id, {})
 
 
     if not category_acl.get('can_change_marked_answers'):
     if not category_acl.get('can_change_marked_answers'):
         raise PermissionDenied(
         raise PermissionDenied(
@@ -247,14 +247,14 @@ def allow_unmark_best_answer(user, target):
         )
         )
 
 
     if category_acl['can_change_marked_answers'] == 1:
     if category_acl['can_change_marked_answers'] == 1:
-        if target.starter_id != user.id:
+        if user_acl["user_id"] != target.starter_id:
             raise PermissionDenied(
             raise PermissionDenied(
                 _(
                 _(
                     "You don't have permission to unmark this best answer because you are not a "
                     "You don't have permission to unmark this best answer because you are not a "
                     "thread starter."
                     "thread starter."
                 )
                 )
             )
             )
-        if not has_time_to_change_answer(user, target):
+        if not has_time_to_change_answer(user_acl, target):
             raise PermissionDenied(
             raise PermissionDenied(
                 ngettext(
                 ngettext(
                     (
                     (
@@ -301,14 +301,14 @@ def allow_unmark_best_answer(user, target):
 can_unmark_best_answer = return_boolean(allow_unmark_best_answer)
 can_unmark_best_answer = return_boolean(allow_unmark_best_answer)
 
 
 
 
-def allow_mark_as_best_answer(user, target):
-    if user.is_anonymous:
+def allow_mark_as_best_answer(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to mark best answers."))
         raise PermissionDenied(_("You have to sign in to mark best answers."))
 
 
     if target.is_event:
     if target.is_event:
         raise PermissionDenied(_("Events can't be marked as best answers."))
         raise PermissionDenied(_("Events can't be marked as best answers."))
 
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {})
+    category_acl = user_acl['categories'].get(target.category_id, {})
 
 
     if not category_acl.get('can_mark_best_answers'):
     if not category_acl.get('can_mark_best_answers'):
         raise PermissionDenied(
         raise PermissionDenied(
@@ -319,7 +319,7 @@ def allow_mark_as_best_answer(user, target):
             }
             }
         )
         )
 
 
-    if category_acl['can_mark_best_answers'] == 1 and target.thread.starter_id != user.id:
+    if category_acl['can_mark_best_answers'] == 1 and user_acl["user_id"] != target.thread.starter_id:
         raise PermissionDenied(
         raise PermissionDenied(
             _(
             _(
                 "You don't have permission to mark best answer in this thread because you "
                 "You don't have permission to mark best answer in this thread because you "
@@ -348,7 +348,7 @@ def allow_mark_as_best_answer(user, target):
 can_mark_as_best_answer = return_boolean(allow_mark_as_best_answer)
 can_mark_as_best_answer = return_boolean(allow_mark_as_best_answer)
 
 
 
 
-def allow_hide_best_answer(user, target):
+def allow_hide_best_answer(user_acl, target):
     if target.is_best_answer:
     if target.is_best_answer:
         raise PermissionDenied(
         raise PermissionDenied(
             _("You can't hide this post because its marked as best answer.")
             _("You can't hide this post because its marked as best answer.")
@@ -358,7 +358,7 @@ def allow_hide_best_answer(user, target):
 can_hide_best_answer = return_boolean(allow_hide_best_answer)
 can_hide_best_answer = return_boolean(allow_hide_best_answer)
 
 
 
 
-def allow_delete_best_answer(user, target):
+def allow_delete_best_answer(user_acl, target):
     if target.is_best_answer:
     if target.is_best_answer:
         raise PermissionDenied(
         raise PermissionDenied(
             _("You can't delete this post because its marked as best answer.")
             _("You can't delete this post because its marked as best answer.")
@@ -368,8 +368,8 @@ def allow_delete_best_answer(user, target):
 can_delete_best_answer = return_boolean(allow_delete_best_answer)
 can_delete_best_answer = return_boolean(allow_delete_best_answer)
 
 
 
 
-def has_time_to_change_answer(user, target):
-    category_acl = user.acl_cache['categories'].get(target.category_id, {})
+def has_time_to_change_answer(user_acl, target):
+    category_acl = user_acl['categories'].get(target.category_id, {})
     change_time = category_acl.get('best_answer_change_time', 0)
     change_time = category_acl.get('best_answer_change_time', 0)
 
 
     if change_time:
     if change_time:

+ 37 - 37
misago/threads/permissions/polls.py

@@ -98,18 +98,18 @@ def build_acl(acl, roles, key_name):
     )
     )
 
 
 
 
-def add_acl_to_poll(user, poll):
+def add_acl_to_poll(user_acl, poll):
     poll.acl.update({
     poll.acl.update({
-        'can_vote': can_vote_poll(user, poll),
-        'can_edit': can_edit_poll(user, poll),
-        'can_delete': can_delete_poll(user, poll),
-        'can_see_votes': can_see_poll_votes(user, poll),
+        'can_vote': can_vote_poll(user_acl, poll),
+        'can_edit': can_edit_poll(user_acl, poll),
+        'can_delete': can_delete_poll(user_acl, poll),
+        'can_see_votes': can_see_poll_votes(user_acl, poll),
     })
     })
 
 
 
 
-def add_acl_to_thread(user, thread):
+def add_acl_to_thread(user_acl, thread):
     thread.acl.update({
     thread.acl.update({
-        'can_start_poll': can_start_poll(user, thread),
+        'can_start_poll': can_start_poll(user_acl, thread),
     })
     })
 
 
 
 
@@ -118,19 +118,19 @@ def register_with(registry):
     registry.acl_annotator(Thread, add_acl_to_thread)
     registry.acl_annotator(Thread, add_acl_to_thread)
 
 
 
 
-def allow_start_poll(user, target):
-    if user.is_anonymous:
+def allow_start_poll(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to start polls."))
         raise PermissionDenied(_("You have to sign in to start polls."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_close_threads': False,
             'can_close_threads': False,
         }
         }
     )
     )
 
 
-    if not user.acl_cache.get('can_start_polls'):
+    if not user_acl.get('can_start_polls'):
         raise PermissionDenied(_("You can't start polls."))
         raise PermissionDenied(_("You can't start polls."))
-    if user.acl_cache.get('can_start_polls') < 2 and user.pk != target.starter_id:
+    if user_acl.get('can_start_polls') < 2 and user_acl["user_id"] != target.starter_id:
         raise PermissionDenied(_("You can't start polls in other users threads."))
         raise PermissionDenied(_("You can't start polls in other users threads."))
 
 
     if not category_acl.get('can_close_threads'):
     if not category_acl.get('can_close_threads'):
@@ -143,29 +143,29 @@ def allow_start_poll(user, target):
 can_start_poll = return_boolean(allow_start_poll)
 can_start_poll = return_boolean(allow_start_poll)
 
 
 
 
-def allow_edit_poll(user, target):
-    if user.is_anonymous:
+def allow_edit_poll(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to edit polls."))
         raise PermissionDenied(_("You have to sign in to edit polls."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_close_threads': False,
             'can_close_threads': False,
         }
         }
     )
     )
 
 
-    if not user.acl_cache.get('can_edit_polls'):
+    if not user_acl.get('can_edit_polls'):
         raise PermissionDenied(_("You can't edit polls."))
         raise PermissionDenied(_("You can't edit polls."))
 
 
-    if user.acl_cache.get('can_edit_polls') < 2:
-        if user.pk != target.poster_id:
+    if user_acl.get('can_edit_polls') < 2:
+        if user_acl["user_id"] != target.poster_id:
             raise PermissionDenied(_("You can't edit other users polls in this category."))
             raise PermissionDenied(_("You can't edit other users polls in this category."))
-        if not has_time_to_edit_poll(user, target):
+        if not has_time_to_edit_poll(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't edit polls that are older than %(minutes)s minute.",
                 "You can't edit polls that are older than %(minutes)s minute.",
                 "You can't edit polls that are older than %(minutes)s minutes.",
                 "You can't edit polls that are older than %(minutes)s minutes.",
-                user.acl_cache['poll_edit_time']
+                user_acl['poll_edit_time']
             )
             )
-            raise PermissionDenied(message % {'minutes': user.acl_cache['poll_edit_time']})
+            raise PermissionDenied(message % {'minutes': user_acl['poll_edit_time']})
 
 
         if target.is_over:
         if target.is_over:
             raise PermissionDenied(_("This poll is over. You can't edit it."))
             raise PermissionDenied(_("This poll is over. You can't edit it."))
@@ -180,29 +180,29 @@ def allow_edit_poll(user, target):
 can_edit_poll = return_boolean(allow_edit_poll)
 can_edit_poll = return_boolean(allow_edit_poll)
 
 
 
 
-def allow_delete_poll(user, target):
-    if user.is_anonymous:
+def allow_delete_poll(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to delete polls."))
         raise PermissionDenied(_("You have to sign in to delete polls."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_close_threads': False,
             'can_close_threads': False,
         }
         }
     )
     )
 
 
-    if not user.acl_cache.get('can_delete_polls'):
+    if not user_acl.get('can_delete_polls'):
         raise PermissionDenied(_("You can't delete polls."))
         raise PermissionDenied(_("You can't delete polls."))
 
 
-    if user.acl_cache.get('can_delete_polls') < 2:
-        if user.pk != target.poster_id:
+    if user_acl.get('can_delete_polls') < 2:
+        if user_acl["user_id"] != target.poster_id:
             raise PermissionDenied(_("You can't delete other users polls in this category."))
             raise PermissionDenied(_("You can't delete other users polls in this category."))
-        if not has_time_to_edit_poll(user, target):
+        if not has_time_to_edit_poll(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't delete polls that are older than %(minutes)s minute.",
                 "You can't delete polls that are older than %(minutes)s minute.",
                 "You can't delete polls that are older than %(minutes)s minutes.",
                 "You can't delete polls that are older than %(minutes)s minutes.",
-                user.acl_cache['poll_edit_time']
+                user_acl['poll_edit_time']
             )
             )
-            raise PermissionDenied(message % {'minutes': user.acl_cache['poll_edit_time']})
+            raise PermissionDenied(message % {'minutes': user_acl['poll_edit_time']})
         if target.is_over:
         if target.is_over:
             raise PermissionDenied(_("This poll is over. You can't delete it."))
             raise PermissionDenied(_("This poll is over. You can't delete it."))
 
 
@@ -216,8 +216,8 @@ def allow_delete_poll(user, target):
 can_delete_poll = return_boolean(allow_delete_poll)
 can_delete_poll = return_boolean(allow_delete_poll)
 
 
 
 
-def allow_vote_poll(user, target):
-    if user.is_anonymous:
+def allow_vote_poll(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to vote in polls."))
         raise PermissionDenied(_("You have to sign in to vote in polls."))
 
 
     if target.has_selected_choices and not target.allow_revotes:
     if target.has_selected_choices and not target.allow_revotes:
@@ -225,7 +225,7 @@ def allow_vote_poll(user, target):
     if target.is_over:
     if target.is_over:
         raise PermissionDenied(_("This poll is over. You can't vote in it."))
         raise PermissionDenied(_("This poll is over. You can't vote in it."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_close_threads': False,
             'can_close_threads': False,
         }
         }
@@ -241,16 +241,16 @@ def allow_vote_poll(user, target):
 can_vote_poll = return_boolean(allow_vote_poll)
 can_vote_poll = return_boolean(allow_vote_poll)
 
 
 
 
-def allow_see_poll_votes(user, target):
-    if not target.is_public and not user.acl_cache['can_always_see_poll_voters']:
+def allow_see_poll_votes(user_acl, target):
+    if not target.is_public and not user_acl['can_always_see_poll_voters']:
         raise PermissionDenied(_("You dont have permission to this poll's voters."))
         raise PermissionDenied(_("You dont have permission to this poll's voters."))
 
 
 
 
 can_see_poll_votes = return_boolean(allow_see_poll_votes)
 can_see_poll_votes = return_boolean(allow_see_poll_votes)
 
 
 
 
-def has_time_to_edit_poll(user, target):
-    edit_time = user.acl_cache['poll_edit_time']
+def has_time_to_edit_poll(user_acl, target):
+    edit_time = user_acl['poll_edit_time']
     if edit_time:
     if edit_time:
         diff = timezone.now() - target.posted_on
         diff = timezone.now() - target.posted_on
         diff_minutes = int(diff.total_seconds() / 60)
         diff_minutes = int(diff.total_seconds() / 60)

+ 26 - 24
misago/threads/permissions/privatethreads.py

@@ -152,7 +152,7 @@ def build_acl(acl, roles, key_name):
     return new_acl
     return new_acl
 
 
 
 
-def add_acl_to_thread(user, thread):
+def add_acl_to_thread(user_acl, thread):
     if thread.thread_type.root_name != PRIVATE_THREADS_ROOT_NAME:
     if thread.thread_type.root_name != PRIVATE_THREADS_ROOT_NAME:
         return
         return
 
 
@@ -162,8 +162,8 @@ def add_acl_to_thread(user, thread):
 
 
     thread.acl.update({
     thread.acl.update({
         'can_start_poll': False,
         'can_start_poll': False,
-        'can_change_owner': can_change_owner(user, thread),
-        'can_add_participants': can_add_participants(user, thread),
+        'can_change_owner': can_change_owner(user_acl, thread),
+        'can_add_participants': can_add_participants(user_acl, thread),
     })
     })
 
 
 
 
@@ -171,23 +171,23 @@ def register_with(registry):
     registry.acl_annotator(Thread, add_acl_to_thread)
     registry.acl_annotator(Thread, add_acl_to_thread)
 
 
 
 
-def allow_use_private_threads(user):
-    if user.is_anonymous:
+def allow_use_private_threads(user_acl):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to use private threads."))
         raise PermissionDenied(_("You have to sign in to use private threads."))
-    if not user.acl_cache['can_use_private_threads']:
+    if not user_acl['can_use_private_threads']:
         raise PermissionDenied(_("You can't use private threads."))
         raise PermissionDenied(_("You can't use private threads."))
 
 
 
 
 can_use_private_threads = return_boolean(allow_use_private_threads)
 can_use_private_threads = return_boolean(allow_use_private_threads)
 
 
 
 
-def allow_see_private_thread(user, target):
-    if user.acl_cache['can_moderate_private_threads']:
+def allow_see_private_thread(user_acl, target):
+    if user_acl['can_moderate_private_threads']:
         can_see_reported = target.has_reported_posts
         can_see_reported = target.has_reported_posts
     else:
     else:
         can_see_reported = False
         can_see_reported = False
 
 
-    can_see_participating = user in [p.user for p in target.participants_list]
+    can_see_participating = user_acl["user_id"] in [p.user_id for p in target.participants_list]
 
 
     if not (can_see_participating or can_see_reported):
     if not (can_see_participating or can_see_reported):
         raise Http404()
         raise Http404()
@@ -196,8 +196,8 @@ def allow_see_private_thread(user, target):
 can_see_private_thread = return_boolean(allow_see_private_thread)
 can_see_private_thread = return_boolean(allow_see_private_thread)
 
 
 
 
-def allow_change_owner(user, target):
-    is_moderator = user.acl_cache['can_moderate_private_threads']
+def allow_change_owner(user_acl, target):
+    is_moderator = user_acl['can_moderate_private_threads']
     is_owner = target.participant and target.participant.is_owner
     is_owner = target.participant and target.participant.is_owner
 
 
     if not (is_owner or is_moderator):
     if not (is_owner or is_moderator):
@@ -210,8 +210,8 @@ def allow_change_owner(user, target):
 can_change_owner = return_boolean(allow_change_owner)
 can_change_owner = return_boolean(allow_change_owner)
 
 
 
 
-def allow_add_participants(user, target):
-    is_moderator = user.acl_cache['can_moderate_private_threads']
+def allow_add_participants(user_acl, target):
+    is_moderator = user_acl['can_moderate_private_threads']
 
 
     if not is_moderator:
     if not is_moderator:
         if not target.participant or not target.participant.is_owner:
         if not target.participant or not target.participant.is_owner:
@@ -220,7 +220,7 @@ def allow_add_participants(user, target):
         if target.is_closed:
         if target.is_closed:
             raise PermissionDenied(_("Only moderators can add participants to closed threads."))
             raise PermissionDenied(_("Only moderators can add participants to closed threads."))
 
 
-    max_participants = user.acl_cache['max_private_thread_participants']
+    max_participants = user_acl['max_private_thread_participants']
     current_participants = len(target.participants_list) - 1
     current_participants = len(target.participants_list) - 1
 
 
     if current_participants >= max_participants:
     if current_participants >= max_participants:
@@ -230,11 +230,11 @@ def allow_add_participants(user, target):
 can_add_participants = return_boolean(allow_add_participants)
 can_add_participants = return_boolean(allow_add_participants)
 
 
 
 
-def allow_remove_participant(user, thread, target):
-    if user.acl_cache['can_moderate_private_threads']:
+def allow_remove_participant(user_acl, thread, target):
+    if user_acl['can_moderate_private_threads']:
         return
         return
 
 
-    if user == target:
+    if user_acl["user_id"] == target.id:
         return  # we can always remove ourselves
         return  # we can always remove ourselves
 
 
     if thread.is_closed:
     if thread.is_closed:
@@ -247,7 +247,7 @@ def allow_remove_participant(user, thread, target):
 can_remove_participant = return_boolean(allow_remove_participant)
 can_remove_participant = return_boolean(allow_remove_participant)
 
 
 
 
-def allow_add_participant(user, target):
+def allow_add_participant(user_acl, target):
     message_format = {'user': target.username}
     message_format = {'user': target.username}
 
 
     if not can_use_private_threads(target):
     if not can_use_private_threads(target):
@@ -255,10 +255,11 @@ def allow_add_participant(user, target):
             _("%(user)s can't participate in private threads.") % message_format
             _("%(user)s can't participate in private threads.") % message_format
         )
         )
 
 
-    if user.acl_cache['can_add_everyone_to_private_threads']:
+    if user_acl['can_add_everyone_to_private_threads']:
         return
         return
 
 
-    if user.acl_cache['can_be_blocked'] and target.is_blocking(user):
+    # FIXME: User.is_blocking() needs to work with ids
+    if user_acl['can_be_blocked'] and target.is_blocking(user_acl["user_id"]):
         raise PermissionDenied(_("%(user)s is blocking you.") % message_format)
         raise PermissionDenied(_("%(user)s is blocking you.") % message_format)
 
 
     if target.can_be_messaged_by_nobody:
     if target.can_be_messaged_by_nobody:
@@ -266,7 +267,8 @@ def allow_add_participant(user, target):
             _("%(user)s is not allowing invitations to private threads.") % message_format
             _("%(user)s is not allowing invitations to private threads.") % message_format
         )
         )
 
 
-    if target.can_be_messaged_by_followed and not target.is_following(user):
+    # FIXME: User.is_following() needs to work with ids
+    if target.can_be_messaged_by_followed and not target.is_following(user_acl["user_id"]):
         message = _("%(user)s limits invitations to private threads to followed users.")
         message = _("%(user)s limits invitations to private threads to followed users.")
         raise PermissionDenied(message % message_format)
         raise PermissionDenied(message % message_format)
 
 
@@ -274,9 +276,9 @@ def allow_add_participant(user, target):
 can_add_participant = return_boolean(allow_add_participant)
 can_add_participant = return_boolean(allow_add_participant)
 
 
 
 
-def allow_message_user(user, target):
-    allow_use_private_threads(user)
-    allow_add_participant(user, target)
+def allow_message_user(user_acl, target):
+    allow_use_private_threads(user_acl)
+    allow_add_participant(user_acl, target)
 
 
 
 
 can_message_user = return_boolean(allow_message_user)
 can_message_user = return_boolean(allow_message_user)

+ 151 - 151
misago/threads/permissions/threads.py

@@ -370,8 +370,8 @@ def build_category_acl(acl, category, categories_roles, key_name):
     return final_acl
     return final_acl
 
 
 
 
-def add_acl_to_category(user, category):
-    category_acl = user.acl_cache['categories'].get(category.pk, {})
+def add_acl_to_category(user_acl, category):
+    category_acl = user_acl['categories'].get(category.pk, {})
 
 
     category.acl.update({
     category.acl.update({
         'can_see_all_threads': 0,
         'can_see_all_threads': 0,
@@ -411,7 +411,7 @@ def add_acl_to_category(user, category):
         can_see_posts_likes=algebra.greater,
         can_see_posts_likes=algebra.greater,
     )
     )
 
 
-    if user.is_authenticated:
+    if user_acl["is_authenticated"]:
         algebra.sum_acls(
         algebra.sum_acls(
             category.acl,
             category.acl,
             acls=[category_acl],
             acls=[category_acl],
@@ -442,7 +442,7 @@ def add_acl_to_category(user, category):
             can_hide_events=algebra.greater,
             can_hide_events=algebra.greater,
         )
         )
 
 
-    if user.acl_cache['can_approve_content']:
+    if user_acl['can_approve_content']:
         category.acl.update({
         category.acl.update({
             'require_threads_approval': 0,
             'require_threads_approval': 0,
             'require_replies_approval': 0,
             'require_replies_approval': 0,
@@ -452,23 +452,23 @@ def add_acl_to_category(user, category):
     category.acl['can_see_own_threads'] = not category.acl['can_see_all_threads']
     category.acl['can_see_own_threads'] = not category.acl['can_see_all_threads']
 
 
 
 
-def add_acl_to_thread(user, thread):
-    category_acl = user.acl_cache['categories'].get(thread.category_id, {})
+def add_acl_to_thread(user_acl, thread):
+    category_acl = user_acl['categories'].get(thread.category_id, {})
 
 
     thread.acl.update({
     thread.acl.update({
-        'can_reply': can_reply_thread(user, thread),
-        'can_edit': can_edit_thread(user, thread),
-        'can_pin': can_pin_thread(user, thread),
+        'can_reply': can_reply_thread(user_acl, thread),
+        'can_edit': can_edit_thread(user_acl, thread),
+        'can_pin': can_pin_thread(user_acl, thread),
         'can_pin_globally': False,
         'can_pin_globally': False,
-        'can_hide': can_hide_thread(user, thread),
-        'can_unhide': can_unhide_thread(user, thread),
-        'can_delete': can_delete_thread(user, thread),
+        'can_hide': can_hide_thread(user_acl, thread),
+        'can_unhide': can_unhide_thread(user_acl, thread),
+        'can_delete': can_delete_thread(user_acl, thread),
         'can_close': category_acl.get('can_close_threads', False),
         'can_close': category_acl.get('can_close_threads', False),
-        'can_move': can_move_thread(user, thread),
-        'can_merge': can_merge_thread(user, thread),
+        'can_move': can_move_thread(user_acl, thread),
+        'can_merge': can_merge_thread(user_acl, thread),
         'can_move_posts': category_acl.get('can_move_posts', False),
         'can_move_posts': category_acl.get('can_move_posts', False),
         'can_merge_posts': category_acl.get('can_merge_posts', False),
         'can_merge_posts': category_acl.get('can_merge_posts', False),
-        'can_approve': can_approve_thread(user, thread),
+        'can_approve': can_approve_thread(user_acl, thread),
         'can_see_reports': category_acl.get('can_see_reports', False),
         'can_see_reports': category_acl.get('can_see_reports', False),
     })
     })
 
 
@@ -476,18 +476,18 @@ def add_acl_to_thread(user, thread):
         thread.acl['can_pin_globally'] = True
         thread.acl['can_pin_globally'] = True
 
 
 
 
-def add_acl_to_post(user, post):
+def add_acl_to_post(user_acl, post):
     if post.is_event:
     if post.is_event:
-        add_acl_to_event(user, post)
+        add_acl_to_event(user_acl, post)
     else:
     else:
-        add_acl_to_reply(user, post)
+        add_acl_to_reply(user_acl, post)
 
 
 
 
-def add_acl_to_event(user, event):
+def add_acl_to_event(user_acl, event):
     can_hide_events = 0
     can_hide_events = 0
 
 
-    if user.is_authenticated:
-        category_acl = user.acl_cache['categories'].get(
+    if user_acl["is_authenticated"]:
+        category_acl = user_acl['categories'].get(
             event.category_id, {
             event.category_id, {
                 'can_hide_events': 0,
                 'can_hide_events': 0,
             }
             }
@@ -497,25 +497,25 @@ def add_acl_to_event(user, event):
 
 
     event.acl.update({
     event.acl.update({
         'can_see_hidden': can_hide_events > 0,
         'can_see_hidden': can_hide_events > 0,
-        'can_hide': can_hide_event(user, event),
-        'can_delete': can_delete_event(user, event),
+        'can_hide': can_hide_event(user_acl, event),
+        'can_delete': can_delete_event(user_acl, event),
     })
     })
 
 
 
 
-def add_acl_to_reply(user, post):
-    category_acl = user.acl_cache['categories'].get(post.category_id, {})
+def add_acl_to_reply(user_acl, post):
+    category_acl = user_acl['categories'].get(post.category_id, {})
 
 
     post.acl.update({
     post.acl.update({
-        'can_reply': can_reply_thread(user, post.thread),
-        'can_edit': can_edit_post(user, post),
+        'can_reply': can_reply_thread(user_acl, post.thread),
+        'can_edit': can_edit_post(user_acl, post),
         'can_see_hidden': post.is_first_post or category_acl.get('can_hide_posts'),
         'can_see_hidden': post.is_first_post or category_acl.get('can_hide_posts'),
-        'can_unhide': can_unhide_post(user, post),
-        'can_hide': can_hide_post(user, post),
-        'can_delete': can_delete_post(user, post),
-        'can_protect': can_protect_post(user, post),
-        'can_approve': can_approve_post(user, post),
-        'can_move': can_move_post(user, post),
-        'can_merge': can_merge_post(user, post),
+        'can_unhide': can_unhide_post(user_acl, post),
+        'can_hide': can_hide_post(user_acl, post),
+        'can_delete': can_delete_post(user_acl, post),
+        'can_protect': can_protect_post(user_acl, post),
+        'can_approve': can_approve_post(user_acl, post),
+        'can_move': can_move_post(user_acl, post),
+        'can_merge': can_merge_post(user_acl, post),
         'can_report': category_acl.get('can_report_content', False),
         'can_report': category_acl.get('can_report_content', False),
         'can_see_reports': category_acl.get('can_see_reports', False),
         'can_see_reports': category_acl.get('can_see_reports', False),
         'can_see_likes': category_acl.get('can_see_posts_likes', 0),
         'can_see_likes': category_acl.get('can_see_posts_likes', 0),
@@ -524,7 +524,7 @@ def add_acl_to_reply(user, post):
 
 
     if not post.acl['can_see_hidden']:
     if not post.acl['can_see_hidden']:
         post.acl['can_see_hidden'] = post.id == post.thread.first_post_id
         post.acl['can_see_hidden'] = post.id == post.thread.first_post_id
-    if user.is_authenticated and post.acl['can_see_likes']:
+    if user_acl["is_authenticated"] and post.acl['can_see_likes']:
         post.acl['can_like'] = category_acl.get('can_like_posts', False)
         post.acl['can_like'] = category_acl.get('can_like_posts', False)
 
 
 
 
@@ -534,8 +534,8 @@ def register_with(registry):
     registry.acl_annotator(Post, add_acl_to_post)
     registry.acl_annotator(Post, add_acl_to_post)
 
 
 
 
-def allow_see_thread(user, target):
-    category_acl = user.acl_cache['categories'].get(
+def allow_see_thread(user_acl, target):
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_see': False,
             'can_see': False,
             'can_browse': False,
             'can_browse': False,
@@ -545,10 +545,10 @@ def allow_see_thread(user, target):
     if not (category_acl['can_see'] and category_acl['can_browse']):
     if not (category_acl['can_see'] and category_acl['can_browse']):
         raise Http404()
         raise Http404()
 
 
-    if target.is_hidden and (user.is_anonymous or not category_acl['can_hide_threads']):
+    if target.is_hidden and (user_acl["is_anonymous"] or not category_acl['can_hide_threads']):
         raise Http404()
         raise Http404()
 
 
-    if user.is_anonymous or user.pk != target.starter_id:
+    if user_acl["is_anonymous"] or user_acl["user_id"] != target.starter_id:
         if not category_acl['can_see_all_threads']:
         if not category_acl['can_see_all_threads']:
             raise Http404()
             raise Http404()
 
 
@@ -559,11 +559,11 @@ def allow_see_thread(user, target):
 can_see_thread = return_boolean(allow_see_thread)
 can_see_thread = return_boolean(allow_see_thread)
 
 
 
 
-def allow_start_thread(user, target):
-    if user.is_anonymous:
+def allow_start_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to start threads."))
         raise PermissionDenied(_("You have to sign in to start threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.pk, {
         target.pk, {
             'can_start_threads': False,
             'can_start_threads': False,
         }
         }
@@ -581,11 +581,11 @@ def allow_start_thread(user, target):
 can_start_thread = return_boolean(allow_start_thread)
 can_start_thread = return_boolean(allow_start_thread)
 
 
 
 
-def allow_reply_thread(user, target):
-    if user.is_anonymous:
+def allow_reply_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to reply threads."))
         raise PermissionDenied(_("You have to sign in to reply threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_reply_threads': False,
             'can_reply_threads': False,
         }
         }
@@ -604,11 +604,11 @@ def allow_reply_thread(user, target):
 can_reply_thread = return_boolean(allow_reply_thread)
 can_reply_thread = return_boolean(allow_reply_thread)
 
 
 
 
-def allow_edit_thread(user, target):
-    if user.is_anonymous:
+def allow_edit_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to edit threads."))
         raise PermissionDenied(_("You have to sign in to edit threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_edit_threads': False,
             'can_edit_threads': False,
         }
         }
@@ -618,10 +618,10 @@ def allow_edit_thread(user, target):
         raise PermissionDenied(_("You can't edit threads in this category."))
         raise PermissionDenied(_("You can't edit threads in this category."))
 
 
     if category_acl['can_edit_threads'] == 1:
     if category_acl['can_edit_threads'] == 1:
-        if target.starter_id != user.pk:
+        if user_acl["user_id"] != target.starter_id:
             raise PermissionDenied(_("You can't edit other users threads in this category."))
             raise PermissionDenied(_("You can't edit other users threads in this category."))
 
 
-        if not has_time_to_edit_thread(user, target):
+        if not has_time_to_edit_thread(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't edit threads that are older than %(minutes)s minute.",
                 "You can't edit threads that are older than %(minutes)s minute.",
                 "You can't edit threads that are older than %(minutes)s minutes.",
                 "You can't edit threads that are older than %(minutes)s minutes.",
@@ -639,11 +639,11 @@ def allow_edit_thread(user, target):
 can_edit_thread = return_boolean(allow_edit_thread)
 can_edit_thread = return_boolean(allow_edit_thread)
 
 
 
 
-def allow_pin_thread(user, target):
-    if user.is_anonymous:
+def allow_pin_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to change threads weights."))
         raise PermissionDenied(_("You have to sign in to change threads weights."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_pin_threads': 0,
             'can_pin_threads': 0,
         }
         }
@@ -662,11 +662,11 @@ def allow_pin_thread(user, target):
 can_pin_thread = return_boolean(allow_pin_thread)
 can_pin_thread = return_boolean(allow_pin_thread)
 
 
 
 
-def allow_unhide_thread(user, target):
-    if user.is_anonymous:
+def allow_unhide_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to hide threads."))
         raise PermissionDenied(_("You have to sign in to hide threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_close_threads': False,
             'can_close_threads': False,
         }
         }
@@ -682,11 +682,11 @@ def allow_unhide_thread(user, target):
 can_unhide_thread = return_boolean(allow_unhide_thread)
 can_unhide_thread = return_boolean(allow_unhide_thread)
 
 
 
 
-def allow_hide_thread(user, target):
-    if user.is_anonymous:
+def allow_hide_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to hide threads."))
         raise PermissionDenied(_("You have to sign in to hide threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_threads': 0,
             'can_hide_threads': 0,
             'can_hide_own_threads': 0,
             'can_hide_own_threads': 0,
@@ -697,10 +697,10 @@ def allow_hide_thread(user, target):
         raise PermissionDenied(_("You can't hide threads in this category."))
         raise PermissionDenied(_("You can't hide threads in this category."))
 
 
     if not category_acl['can_hide_threads'] and category_acl['can_hide_own_threads']:
     if not category_acl['can_hide_threads'] and category_acl['can_hide_own_threads']:
-        if user.id != target.starter_id:
+        if user_acl["user_id"] != target.starter_id:
             raise PermissionDenied(_("You can't hide other users theads in this category."))
             raise PermissionDenied(_("You can't hide other users theads in this category."))
 
 
-        if not has_time_to_edit_thread(user, target):
+        if not has_time_to_edit_thread(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't hide threads that are older than %(minutes)s minute.",
                 "You can't hide threads that are older than %(minutes)s minute.",
                 "You can't hide threads that are older than %(minutes)s minutes.",
                 "You can't hide threads that are older than %(minutes)s minutes.",
@@ -718,11 +718,11 @@ def allow_hide_thread(user, target):
 can_hide_thread = return_boolean(allow_hide_thread)
 can_hide_thread = return_boolean(allow_hide_thread)
 
 
 
 
-def allow_delete_thread(user, target):
-    if user.is_anonymous:
+def allow_delete_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to delete threads."))
         raise PermissionDenied(_("You have to sign in to delete threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_threads': 0,
             'can_hide_threads': 0,
             'can_hide_own_threads': 0,
             'can_hide_own_threads': 0,
@@ -733,10 +733,10 @@ def allow_delete_thread(user, target):
         raise PermissionDenied(_("You can't delete threads in this category."))
         raise PermissionDenied(_("You can't delete threads in this category."))
 
 
     if category_acl['can_hide_threads'] != 2 and category_acl['can_hide_own_threads'] == 2:
     if category_acl['can_hide_threads'] != 2 and category_acl['can_hide_own_threads'] == 2:
-        if user.id != target.starter_id:
+        if user_acl["user_id"] != target.starter_id:
             raise PermissionDenied(_("You can't delete other users theads in this category."))
             raise PermissionDenied(_("You can't delete other users theads in this category."))
 
 
-        if not has_time_to_edit_thread(user, target):
+        if not has_time_to_edit_thread(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't delete threads that are older than %(minutes)s minute.",
                 "You can't delete threads that are older than %(minutes)s minute.",
                 "You can't delete threads that are older than %(minutes)s minutes.",
                 "You can't delete threads that are older than %(minutes)s minutes.",
@@ -754,11 +754,11 @@ def allow_delete_thread(user, target):
 can_delete_thread = return_boolean(allow_delete_thread)
 can_delete_thread = return_boolean(allow_delete_thread)
 
 
 
 
-def allow_move_thread(user, target):
-    if user.is_anonymous:
+def allow_move_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to move threads."))
         raise PermissionDenied(_("You have to sign in to move threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_move_threads': 0,
             'can_move_threads': 0,
         }
         }
@@ -777,11 +777,11 @@ def allow_move_thread(user, target):
 can_move_thread = return_boolean(allow_move_thread)
 can_move_thread = return_boolean(allow_move_thread)
 
 
 
 
-def allow_merge_thread(user, target, otherthread=False):
-    if user.is_anonymous:
+def allow_merge_thread(user_acl, target, otherthread=False):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to merge threads."))
         raise PermissionDenied(_("You have to sign in to merge threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_merge_threads': 0,
             'can_merge_threads': 0,
         }
         }
@@ -806,11 +806,11 @@ def allow_merge_thread(user, target, otherthread=False):
 can_merge_thread = return_boolean(allow_merge_thread)
 can_merge_thread = return_boolean(allow_merge_thread)
 
 
 
 
-def allow_approve_thread(user, target):
-    if user.is_anonymous:
+def allow_approve_thread(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to approve threads."))
         raise PermissionDenied(_("You have to sign in to approve threads."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_approve_content': 0,
             'can_approve_content': 0,
         }
         }
@@ -829,8 +829,8 @@ def allow_approve_thread(user, target):
 can_approve_thread = return_boolean(allow_approve_thread)
 can_approve_thread = return_boolean(allow_approve_thread)
 
 
 
 
-def allow_see_post(user, target):
-    category_acl = user.acl_cache['categories'].get(
+def allow_see_post(user_acl, target):
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_approve_content': False,
             'can_approve_content': False,
             'can_hide_events': False,
             'can_hide_events': False,
@@ -838,10 +838,10 @@ def allow_see_post(user, target):
     )
     )
 
 
     if not target.is_event and target.is_unapproved:
     if not target.is_event and target.is_unapproved:
-        if user.is_anonymous:
+        if user_acl["is_anonymous"]:
             raise Http404()
             raise Http404()
 
 
-        if not category_acl['can_approve_content'] and user.id != target.poster_id:
+        if not category_acl['can_approve_content'] and user_acl["user_id"] != target.poster_id:
             raise Http404()
             raise Http404()
 
 
     if target.is_event and target.is_hidden and not category_acl['can_hide_events']:
     if target.is_event and target.is_hidden and not category_acl['can_hide_events']:
@@ -851,14 +851,14 @@ def allow_see_post(user, target):
 can_see_post = return_boolean(allow_see_post)
 can_see_post = return_boolean(allow_see_post)
 
 
 
 
-def allow_edit_post(user, target):
-    if user.is_anonymous:
+def allow_edit_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to edit posts."))
         raise PermissionDenied(_("You have to sign in to edit posts."))
 
 
     if target.is_event:
     if target.is_event:
         raise PermissionDenied(_("Events can't be edited."))
         raise PermissionDenied(_("Events can't be edited."))
 
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {'can_edit_posts': False})
+    category_acl = user_acl['categories'].get(target.category_id, {'can_edit_posts': False})
 
 
     if not category_acl['can_edit_posts']:
     if not category_acl['can_edit_posts']:
         raise PermissionDenied(_("You can't edit posts in this category."))
         raise PermissionDenied(_("You can't edit posts in this category."))
@@ -867,13 +867,13 @@ def allow_edit_post(user, target):
         raise PermissionDenied(_("This post is hidden, you can't edit it."))
         raise PermissionDenied(_("This post is hidden, you can't edit it."))
 
 
     if category_acl['can_edit_posts'] == 1:
     if category_acl['can_edit_posts'] == 1:
-        if target.poster_id != user.pk:
+        if target.poster_id != user_acl["user_id"]:
             raise PermissionDenied(_("You can't edit other users posts in this category."))
             raise PermissionDenied(_("You can't edit other users posts in this category."))
 
 
         if target.is_protected and not category_acl['can_protect_posts']:
         if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(_("This post is protected. You can't edit it."))
             raise PermissionDenied(_("This post is protected. You can't edit it."))
 
 
-        if not has_time_to_edit_post(user, target):
+        if not has_time_to_edit_post(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't edit posts that are older than %(minutes)s minute.",
                 "You can't edit posts that are older than %(minutes)s minute.",
                 "You can't edit posts that are older than %(minutes)s minutes.",
                 "You can't edit posts that are older than %(minutes)s minutes.",
@@ -891,11 +891,11 @@ def allow_edit_post(user, target):
 can_edit_post = return_boolean(allow_edit_post)
 can_edit_post = return_boolean(allow_edit_post)
 
 
 
 
-def allow_unhide_post(user, target):
-    if user.is_anonymous:
+def allow_unhide_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to reveal posts."))
         raise PermissionDenied(_("You have to sign in to reveal posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
@@ -906,13 +906,13 @@ def allow_unhide_post(user, target):
         if not category_acl['can_hide_own_posts']:
         if not category_acl['can_hide_own_posts']:
             raise PermissionDenied(_("You can't reveal posts in this category."))
             raise PermissionDenied(_("You can't reveal posts in this category."))
 
 
-        if user.id != target.poster_id:
+        if user_acl["user_id"] != target.poster_id:
             raise PermissionDenied(_("You can't reveal other users posts in this category."))
             raise PermissionDenied(_("You can't reveal other users posts in this category."))
 
 
         if target.is_protected and not category_acl['can_protect_posts']:
         if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(_("This post is protected. You can't reveal it."))
             raise PermissionDenied(_("This post is protected. You can't reveal it."))
 
 
-        if not has_time_to_edit_post(user, target):
+        if not has_time_to_edit_post(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't reveal posts that are older than %(minutes)s minute.",
                 "You can't reveal posts that are older than %(minutes)s minute.",
                 "You can't reveal posts that are older than %(minutes)s minutes.",
                 "You can't reveal posts that are older than %(minutes)s minutes.",
@@ -933,11 +933,11 @@ def allow_unhide_post(user, target):
 can_unhide_post = return_boolean(allow_unhide_post)
 can_unhide_post = return_boolean(allow_unhide_post)
 
 
 
 
-def allow_hide_post(user, target):
-    if user.is_anonymous:
+def allow_hide_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to hide posts."))
         raise PermissionDenied(_("You have to sign in to hide posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
@@ -948,13 +948,13 @@ def allow_hide_post(user, target):
         if not category_acl['can_hide_own_posts']:
         if not category_acl['can_hide_own_posts']:
             raise PermissionDenied(_("You can't hide posts in this category."))
             raise PermissionDenied(_("You can't hide posts in this category."))
 
 
-        if user.id != target.poster_id:
+        if user_acl["user_id"] != target.poster_id:
             raise PermissionDenied(_("You can't hide other users posts in this category."))
             raise PermissionDenied(_("You can't hide other users posts in this category."))
 
 
         if target.is_protected and not category_acl['can_protect_posts']:
         if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(_("This post is protected. You can't hide it."))
             raise PermissionDenied(_("This post is protected. You can't hide it."))
 
 
-        if not has_time_to_edit_post(user, target):
+        if not has_time_to_edit_post(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't hide posts that are older than %(minutes)s minute.",
                 "You can't hide posts that are older than %(minutes)s minute.",
                 "You can't hide posts that are older than %(minutes)s minutes.",
                 "You can't hide posts that are older than %(minutes)s minutes.",
@@ -975,11 +975,11 @@ def allow_hide_post(user, target):
 can_hide_post = return_boolean(allow_hide_post)
 can_hide_post = return_boolean(allow_hide_post)
 
 
 
 
-def allow_delete_post(user, target):
-    if user.is_anonymous:
+def allow_delete_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to delete posts."))
         raise PermissionDenied(_("You have to sign in to delete posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
@@ -990,13 +990,13 @@ def allow_delete_post(user, target):
         if category_acl['can_hide_own_posts'] != 2:
         if category_acl['can_hide_own_posts'] != 2:
             raise PermissionDenied(_("You can't delete posts in this category."))
             raise PermissionDenied(_("You can't delete posts in this category."))
 
 
-        if user.id != target.poster_id:
+        if user_acl["user_id"] != target.poster_id:
             raise PermissionDenied(_("You can't delete other users posts in this category."))
             raise PermissionDenied(_("You can't delete other users posts in this category."))
 
 
         if target.is_protected and not category_acl['can_protect_posts']:
         if target.is_protected and not category_acl['can_protect_posts']:
             raise PermissionDenied(_("This post is protected. You can't delete it."))
             raise PermissionDenied(_("This post is protected. You can't delete it."))
 
 
-        if not has_time_to_edit_post(user, target):
+        if not has_time_to_edit_post(user_acl, target):
             message = ngettext(
             message = ngettext(
                 "You can't delete posts that are older than %(minutes)s minute.",
                 "You can't delete posts that are older than %(minutes)s minute.",
                 "You can't delete posts that are older than %(minutes)s minutes.",
                 "You can't delete posts that are older than %(minutes)s minutes.",
@@ -1017,28 +1017,28 @@ def allow_delete_post(user, target):
 can_delete_post = return_boolean(allow_delete_post)
 can_delete_post = return_boolean(allow_delete_post)
 
 
 
 
-def allow_protect_post(user, target):
-    if user.is_anonymous:
+def allow_protect_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to protect posts."))
         raise PermissionDenied(_("You have to sign in to protect posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {'can_protect_posts': False}
         target.category_id, {'can_protect_posts': False}
     )
     )
 
 
     if not category_acl['can_protect_posts']:
     if not category_acl['can_protect_posts']:
         raise PermissionDenied(_("You can't protect posts in this category."))
         raise PermissionDenied(_("You can't protect posts in this category."))
-    if not can_edit_post(user, target):
+    if not can_edit_post(user_acl, target):
         raise PermissionDenied(_("You can't protect posts you can't edit."))
         raise PermissionDenied(_("You can't protect posts you can't edit."))
 
 
 
 
 can_protect_post = return_boolean(allow_protect_post)
 can_protect_post = return_boolean(allow_protect_post)
 
 
 
 
-def allow_approve_post(user, target):
-    if user.is_anonymous:
+def allow_approve_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to approve posts."))
         raise PermissionDenied(_("You have to sign in to approve posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {'can_approve_content': False}
         target.category_id, {'can_approve_content': False}
     )
     )
 
 
@@ -1059,11 +1059,11 @@ def allow_approve_post(user, target):
 can_approve_post = return_boolean(allow_approve_post)
 can_approve_post = return_boolean(allow_approve_post)
 
 
 
 
-def allow_move_post(user, target):
-    if user.is_anonymous:
+def allow_move_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to move posts."))
         raise PermissionDenied(_("You have to sign in to move posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_move_posts': False,
             'can_move_posts': False,
         }
         }
@@ -1088,11 +1088,11 @@ def allow_move_post(user, target):
 can_move_post = return_boolean(allow_move_post)
 can_move_post = return_boolean(allow_move_post)
 
 
 
 
-def allow_merge_post(user, target):
-    if user.is_anonymous:
+def allow_merge_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to merge posts."))
         raise PermissionDenied(_("You have to sign in to merge posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_merge_posts': False,
             'can_merge_posts': False,
         }
         }
@@ -1115,11 +1115,11 @@ def allow_merge_post(user, target):
 can_merge_post = return_boolean(allow_merge_post)
 can_merge_post = return_boolean(allow_merge_post)
 
 
 
 
-def allow_split_post(user, target):
-    if user.is_anonymous:
+def allow_split_post(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to split posts."))
         raise PermissionDenied(_("You have to sign in to split posts."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_move_posts': False,
             'can_move_posts': False,
         }
         }
@@ -1143,11 +1143,11 @@ def allow_split_post(user, target):
 can_split_post = return_boolean(allow_split_post)
 can_split_post = return_boolean(allow_split_post)
 
 
 
 
-def allow_unhide_event(user, target):
-    if user.is_anonymous:
+def allow_unhide_event(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to reveal events."))
         raise PermissionDenied(_("You have to sign in to reveal events."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_events': 0,
             'can_hide_events': 0,
         }
         }
@@ -1166,11 +1166,11 @@ def allow_unhide_event(user, target):
 can_unhide_event = return_boolean(allow_unhide_event)
 can_unhide_event = return_boolean(allow_unhide_event)
 
 
 
 
-def allow_hide_event(user, target):
-    if user.is_anonymous:
+def allow_hide_event(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to hide events."))
         raise PermissionDenied(_("You have to sign in to hide events."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_events': 0,
             'can_hide_events': 0,
         }
         }
@@ -1189,11 +1189,11 @@ def allow_hide_event(user, target):
 can_hide_event = return_boolean(allow_hide_event)
 can_hide_event = return_boolean(allow_hide_event)
 
 
 
 
-def allow_delete_event(user, target):
-    if user.is_anonymous:
+def allow_delete_event(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to delete events."))
         raise PermissionDenied(_("You have to sign in to delete events."))
 
 
-    category_acl = user.acl_cache['categories'].get(
+    category_acl = user_acl['categories'].get(
         target.category_id, {
         target.category_id, {
             'can_hide_events': 0,
             'can_hide_events': 0,
         }
         }
@@ -1212,18 +1212,18 @@ def allow_delete_event(user, target):
 can_delete_event = return_boolean(allow_delete_event)
 can_delete_event = return_boolean(allow_delete_event)
 
 
 
 
-def can_change_owned_thread(user, target):
-    if user.is_anonymous or user.pk != target.starter_id:
+def can_change_owned_thread(user_acl, target):
+    if user_acl["is_anonymous"] or user_acl["user_id"] != target.starter_id:
         return False
         return False
 
 
     if target.category.is_closed or target.is_closed:
     if target.category.is_closed or target.is_closed:
         return False
         return False
 
 
-    return has_time_to_edit_thread(user, target)
+    return has_time_to_edit_thread(user_acl, target)
 
 
 
 
-def has_time_to_edit_thread(user, target):
-    edit_time = user.acl_cache['categories'].get(target.category_id, {}).get('thread_edit_time', 0)
+def has_time_to_edit_thread(user_acl, target):
+    edit_time = user_acl['categories'].get(target.category_id, {}).get('thread_edit_time', 0)
     if edit_time:
     if edit_time:
         diff = timezone.now() - target.started_on
         diff = timezone.now() - target.started_on
         diff_minutes = int(diff.total_seconds() / 60)
         diff_minutes = int(diff.total_seconds() / 60)
@@ -1232,8 +1232,8 @@ def has_time_to_edit_thread(user, target):
         return True
         return True
 
 
 
 
-def has_time_to_edit_post(user, target):
-    edit_time = user.acl_cache['categories'].get(target.category_id, {}).get('post_edit_time', 0)
+def has_time_to_edit_post(user_acl, target):
+    edit_time = user_acl['categories'].get(target.category_id, {}).get('post_edit_time', 0)
     if edit_time:
     if edit_time:
         diff = timezone.now() - target.posted_on
         diff = timezone.now() - target.posted_on
         diff_minutes = int(diff.total_seconds() / 60)
         diff_minutes = int(diff.total_seconds() / 60)
@@ -1242,7 +1242,7 @@ def has_time_to_edit_post(user, target):
         return True
         return True
 
 
 
 
-def exclude_invisible_threads(user, categories, queryset):
+def exclude_invisible_threads(user_acl, categories, queryset):
     show_all = []
     show_all = []
     show_accepted_visible = []
     show_accepted_visible = []
     show_accepted = []
     show_accepted = []
@@ -1251,7 +1251,7 @@ def exclude_invisible_threads(user, categories, queryset):
     show_owned_visible = []
     show_owned_visible = []
 
 
     for category in categories:
     for category in categories:
-        add_acl(user, category)
+        add_acl(user_acl, category)
 
 
         if not (category.acl['can_see'] and category.acl['can_browse']):
         if not (category.acl['can_see'] and category.acl['can_browse']):
             continue
             continue
@@ -1262,7 +1262,7 @@ def exclude_invisible_threads(user, categories, queryset):
 
 
             if can_mod and can_hide:
             if can_mod and can_hide:
                 show_all.append(category)
                 show_all.append(category)
-            elif user.is_authenticated:
+            elif user_acl["is_authenticated"]:
                 if not can_mod and not can_hide:
                 if not can_mod and not can_hide:
                     show_accepted_visible.append(category)
                     show_accepted_visible.append(category)
                 elif not can_mod:
                 elif not can_mod:
@@ -1271,7 +1271,7 @@ def exclude_invisible_threads(user, categories, queryset):
                     show_visible.append(category)
                     show_visible.append(category)
             else:
             else:
                 show_accepted_visible.append(category)
                 show_accepted_visible.append(category)
-        elif user.is_authenticated:
+        elif user_acl["is_authenticated"]:
             if can_hide:
             if can_hide:
                 show_owned.append(category)
                 show_owned.append(category)
             else:
             else:
@@ -1282,9 +1282,9 @@ def exclude_invisible_threads(user, categories, queryset):
         conditions = Q(category__in=show_all)
         conditions = Q(category__in=show_all)
 
 
     if show_accepted_visible:
     if show_accepted_visible:
-        if user.is_authenticated:
+        if user_acl["is_authenticated"]:
             condition = Q(
             condition = Q(
-                Q(starter=user) | Q(is_unapproved=False),
+                Q(starter_id=user_acl["user_id"]) | Q(is_unapproved=False),
                 category__in=show_accepted_visible,
                 category__in=show_accepted_visible,
                 is_hidden=False,
                 is_hidden=False,
             )
             )
@@ -1302,7 +1302,7 @@ def exclude_invisible_threads(user, categories, queryset):
 
 
     if show_accepted:
     if show_accepted:
         condition = Q(
         condition = Q(
-            Q(starter=user) | Q(is_unapproved=False),
+            Q(starter_id=user_acl["user_id"]) | Q(is_unapproved=False),
             category__in=show_accepted,
             category__in=show_accepted,
         )
         )
 
 
@@ -1320,7 +1320,7 @@ def exclude_invisible_threads(user, categories, queryset):
             conditions = condition
             conditions = condition
 
 
     if show_owned:
     if show_owned:
-        condition = Q(category__in=show_owned, starter=user)
+        condition = Q(category__in=show_owned, starter_id=user_acl["user_id"])
 
 
         if conditions:
         if conditions:
             conditions = conditions | condition
             conditions = conditions | condition
@@ -1330,7 +1330,7 @@ def exclude_invisible_threads(user, categories, queryset):
     if show_owned_visible:
     if show_owned_visible:
         condition = Q(
         condition = Q(
             category__in=show_owned_visible,
             category__in=show_owned_visible,
-            starter=user,
+            starter_id=user_acl["user_id"],
             is_hidden=False,
             is_hidden=False,
         )
         )
 
 
@@ -1345,14 +1345,14 @@ def exclude_invisible_threads(user, categories, queryset):
         return Thread.objects.none()
         return Thread.objects.none()
 
 
 
 
-def exclude_invisible_posts(user, categories, queryset):
+def exclude_invisible_posts(user_acl, categories, queryset):
     if hasattr(categories, '__iter__'):
     if hasattr(categories, '__iter__'):
-        return exclude_invisible_posts_in_categories(user, categories, queryset)
+        return exclude_invisible_posts_in_categories(user_acl, categories, queryset)
     else:
     else:
-        return exclude_invisible_posts_in_category(user, categories, queryset)
+        return exclude_invisible_posts_in_category(user_acl, categories, queryset)
 
 
 
 
-def exclude_invisible_posts_in_categories(user, categories, queryset):
+def exclude_invisible_posts_in_categories(user_acl, categories, queryset):
     show_all = []
     show_all = []
     show_approved = []
     show_approved = []
     show_approved_owned = []
     show_approved_owned = []
@@ -1360,12 +1360,12 @@ def exclude_invisible_posts_in_categories(user, categories, queryset):
     hide_invisible_events = []
     hide_invisible_events = []
 
 
     for category in categories:
     for category in categories:
-        add_acl(user, category)
+        add_acl(user_acl, category)
 
 
         if category.acl['can_approve_content']:
         if category.acl['can_approve_content']:
             show_all.append(category.pk)
             show_all.append(category.pk)
         else:
         else:
-            if user.is_authenticated:
+            if user_acl["is_authenticated"]:
                 show_approved_owned.append(category.pk)
                 show_approved_owned.append(category.pk)
             else:
             else:
                 show_approved.append(category.pk)
                 show_approved.append(category.pk)
@@ -1390,7 +1390,7 @@ def exclude_invisible_posts_in_categories(user, categories, queryset):
 
 
     if show_approved_owned:
     if show_approved_owned:
         condition = Q(
         condition = Q(
-            Q(poster=user) | Q(is_unapproved=False),
+            Q(poster_id=user_acl["user_id"]) | Q(is_unapproved=False),
             category__in=show_approved_owned,
             category__in=show_approved_owned,
         )
         )
 
 
@@ -1412,12 +1412,12 @@ def exclude_invisible_posts_in_categories(user, categories, queryset):
         return Post.objects.none()
         return Post.objects.none()
 
 
 
 
-def exclude_invisible_posts_in_category(user, category, queryset):
-    add_acl(user, category)
+def exclude_invisible_posts_in_category(user_acl, category, queryset):
+    add_acl(user_acl, category)
 
 
     if not category.acl['can_approve_content']:
     if not category.acl['can_approve_content']:
-        if user.is_authenticated:
-            queryset = queryset.filter(Q(is_unapproved=False) | Q(poster=user))
+        if user_acl["is_authenticated"]:
+            queryset = queryset.filter(Q(is_unapproved=False) | Q(poster_id=user_acl["user_id"]))
         else:
         else:
             queryset = queryset.exclude(is_unapproved=True)
             queryset = queryset.exclude(is_unapproved=True)
 
 

+ 1 - 1
misago/threads/search.py

@@ -27,7 +27,7 @@ class SearchThreads(SearchProvider):
 
 
         if len(query) > 2:
         if len(query) > 2:
             visible_threads = exclude_invisible_threads(
             visible_threads = exclude_invisible_threads(
-                self.request.user, threads_categories, Thread.objects
+                self.request.user_acl, threads_categories, Thread.objects
             )
             )
             results = search_threads(self.request, query, visible_threads)
             results = search_threads(self.request, query, visible_threads)
         else:
         else:

+ 2 - 2
misago/threads/viewmodels/category.py

@@ -15,7 +15,7 @@ __all__ = ['ThreadsRootCategory', 'ThreadsCategory', 'PrivateThreadsCategory']
 class ViewModel(BaseViewModel):
 class ViewModel(BaseViewModel):
     def __init__(self, request, **kwargs):
     def __init__(self, request, **kwargs):
         self._categories = self.get_categories(request)
         self._categories = self.get_categories(request)
-        add_acl(request.user, self._categories)
+        add_acl(request.user_acl, self._categories)
 
 
         self._model = self.get_category(request, self._categories, **kwargs)
         self._model = self.get_category(request, self._categories, **kwargs)
 
 
@@ -51,7 +51,7 @@ class ThreadsRootCategory(ViewModel):
     def get_categories(self, request):
     def get_categories(self, request):
         return [Category.objects.root_category()] + list(
         return [Category.objects.root_category()] + list(
             Category.objects.all_categories().filter(
             Category.objects.all_categories().filter(
-                id__in=request.user.acl_cache['visible_categories'],
+                id__in=request.user_acl['visible_categories'],
             ).select_related('parent')
             ).select_related('parent')
         )
         )
 
 

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

@@ -12,7 +12,7 @@ class ViewModel(BaseViewModel):
     def __init__(self, request, thread, pk):
     def __init__(self, request, thread, pk):
         model = self.get_post(request, thread, pk)
         model = self.get_post(request, thread, pk)
 
 
-        add_acl(request.user, model)
+        add_acl(request.user_acl, model)
 
 
         self._model = model
         self._model = model
 
 

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

@@ -61,7 +61,7 @@ class ViewModel(object):
             posts.sort(key=lambda p: p.pk)
             posts.sort(key=lambda p: p.pk)
 
 
         # make posts and events ACL and reads aware
         # make posts and events ACL and reads aware
-        add_acl(request.user, posts)
+        add_acl(request.user_acl, posts)
         make_read_aware(request.user, posts)
         make_read_aware(request.user, posts)
 
 
         self._user = request.user
         self._user = request.user

+ 6 - 6
misago/threads/viewmodels/thread.py

@@ -44,8 +44,8 @@ class ViewModel(BaseViewModel):
         if path_aware:
         if path_aware:
             model.path = self.get_thread_path(model.category)
             model.path = self.get_thread_path(model.category)
 
 
-        add_acl(request.user, model.category)
-        add_acl(request.user, model)
+        add_acl(request.user_acl, model.category)
+        add_acl(request.user_acl, model)
 
 
         if read_aware:
         if read_aware:
             make_read_aware(request.user, model)
             make_read_aware(request.user, model)
@@ -56,7 +56,7 @@ class ViewModel(BaseViewModel):
 
 
         try:
         try:
             self._poll = model.poll
             self._poll = model.poll
-            add_acl(request.user, self._poll)
+            add_acl(request.user_acl, self._poll)
 
 
             if poll_votes_aware:
             if poll_votes_aware:
                 self._poll.make_choices_votes_aware(request.user)
                 self._poll.make_choices_votes_aware(request.user)
@@ -109,7 +109,7 @@ class ForumThread(ViewModel):
             category__tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME),
             category__tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME),
         )
         )
 
 
-        allow_see_thread(request.user, thread)
+        allow_see_thread(request.user_acl, thread)
         if slug:
         if slug:
             validate_slug(thread, slug)
             validate_slug(thread, slug)
         return thread
         return thread
@@ -123,7 +123,7 @@ class ForumThread(ViewModel):
 
 
 class PrivateThread(ViewModel):
 class PrivateThread(ViewModel):
     def get_thread(self, request, pk, slug=None):
     def get_thread(self, request, pk, slug=None):
-        allow_use_private_threads(request.user)
+        allow_use_private_threads(request.user_acl)
 
 
         thread = get_object_or_404(
         thread = get_object_or_404(
             Thread.objects.select_related(*BASE_RELATIONS),
             Thread.objects.select_related(*BASE_RELATIONS),
@@ -132,7 +132,7 @@ class PrivateThread(ViewModel):
         )
         )
 
 
         make_participants_aware(request.user, thread)
         make_participants_aware(request.user, thread)
-        allow_see_private_thread(request.user, thread)
+        allow_see_private_thread(request.user_acl, thread)
 
 
         if slug:
         if slug:
             validate_slug(thread, slug)
             validate_slug(thread, slug)

+ 7 - 8
misago/threads/viewmodels/threads.py

@@ -19,7 +19,6 @@ from misago.threads.serializers import ThreadsListSerializer
 from misago.threads.subscriptions import make_subscription_aware
 from misago.threads.subscriptions import make_subscription_aware
 from misago.threads.utils import add_categories_to_items
 from misago.threads.utils import add_categories_to_items
 
 
-
 __all__ = ['ForumThreads', 'PrivateThreads', 'filter_read_threads_queryset']
 __all__ = ['ForumThreads', 'PrivateThreads', 'filter_read_threads_queryset']
 
 
 LISTS_NAMES = {
 LISTS_NAMES = {
@@ -69,7 +68,7 @@ class ViewModel(object):
             threads = list(pinned_threads) + list(list_page.object_list)
             threads = list(pinned_threads) + list(list_page.object_list)
 
 
         add_categories_to_items(category_model, category.categories, threads)
         add_categories_to_items(category_model, category.categories, threads)
-        add_acl(request.user, threads)
+        add_acl(request.user_acl, threads)
         make_subscription_aware(request.user, threads)
         make_subscription_aware(request.user, threads)
 
 
         if list_type in ('new', 'unread'):
         if list_type in ('new', 'unread'):
@@ -96,7 +95,7 @@ class ViewModel(object):
             if list_type in LIST_DENIED_MESSAGES:
             if list_type in LIST_DENIED_MESSAGES:
                 raise PermissionDenied(LIST_DENIED_MESSAGES[list_type])
                 raise PermissionDenied(LIST_DENIED_MESSAGES[list_type])
         else:
         else:
-            has_permission = request.user.acl_cache['can_see_unapproved_content_lists']
+            has_permission = request.user_acl['can_see_unapproved_content_lists']
             if list_type == 'unapproved' and not has_permission:
             if list_type == 'unapproved' and not has_permission:
                 raise PermissionDenied(
                 raise PermissionDenied(
                     _("You don't have permission to see unapproved content lists.")
                     _("You don't have permission to see unapproved content lists.")
@@ -107,7 +106,7 @@ class ViewModel(object):
 
 
     def get_base_queryset(self, request, threads_categories, list_type):
     def get_base_queryset(self, request, threads_categories, list_type):
         return get_threads_queryset(
         return get_threads_queryset(
-            request.user,
+            request,
             threads_categories,
             threads_categories,
             list_type,
             list_type,
         ).order_by('-last_post_id')
         ).order_by('-last_post_id')
@@ -169,7 +168,7 @@ class PrivateThreads(ViewModel):
         # limit queryset to threads we are participant of
         # limit queryset to threads we are participant of
         participated_threads = request.user.threadparticipant_set.values('thread_id')
         participated_threads = request.user.threadparticipant_set.values('thread_id')
 
 
-        if request.user.acl_cache['can_moderate_private_threads']:
+        if request.user_acl['can_moderate_private_threads']:
             queryset = queryset.filter(Q(id__in=participated_threads) | Q(has_reported_posts=True))
             queryset = queryset.filter(Q(id__in=participated_threads) | Q(has_reported_posts=True))
         else:
         else:
             queryset = queryset.filter(id__in=participated_threads)
             queryset = queryset.filter(id__in=participated_threads)
@@ -183,13 +182,13 @@ class PrivateThreads(ViewModel):
         make_participants_aware(request.user, threads)
         make_participants_aware(request.user, threads)
 
 
 
 
-def get_threads_queryset(user, categories, list_type):
-    queryset = exclude_invisible_threads(user, categories, Thread.objects)
+def get_threads_queryset(request, categories, list_type):
+    queryset = exclude_invisible_threads(request.user_acl, categories, Thread.objects)
 
 
     if list_type == 'all':
     if list_type == 'all':
         return queryset
         return queryset
     else:
     else:
-        return filter_threads_queryset(user, categories, list_type, queryset)
+        return filter_threads_queryset(request.user, categories, list_type, queryset)
 
 
 
 
 def filter_threads_queryset(user, categories, list_type, queryset):
 def filter_threads_queryset(user, categories, list_type, queryset):

+ 1 - 1
misago/threads/views/attachment.py

@@ -50,7 +50,7 @@ def allow_file_download(request, attachment):
     if not is_authenticated or request.user.id != attachment.uploader_id:
     if not is_authenticated or request.user.id != attachment.uploader_id:
         if not attachment.post_id:
         if not attachment.post_id:
             raise Http404()
             raise Http404()
-        if not request.user.acl_cache['can_download_other_users_attachments']:
+        if not request.user_acl['can_download_other_users_attachments']:
             raise PermissionDenied()
             raise PermissionDenied()
 
 
     allowed_roles = set(r.pk for r in attachment.filetype.limit_downloads_to.all())
     allowed_roles = set(r.pk for r in attachment.filetype.limit_downloads_to.all())

+ 3 - 3
misago/users/api/userendpoints/signature.py

@@ -11,11 +11,11 @@ from misago.users.signatures import is_user_signature_valid, set_user_signature
 
 
 
 
 def signature_endpoint(request):
 def signature_endpoint(request):
-    user = request.user
-
-    if not user.acl_cache['can_have_signature']:
+    if not request.user_acl['can_have_signature']:
         raise PermissionDenied(_("You don't have permission to change signature."))
         raise PermissionDenied(_("You don't have permission to change signature."))
 
 
+    user = request.user
+    
     if user.is_signature_locked:
     if user.is_signature_locked:
         if user.signature_lock_user_message:
         if user.signature_lock_user_message:
             reason = format_plaintext_for_html(user.signature_lock_user_message)
             reason = format_plaintext_for_html(user.signature_lock_user_message)

+ 1 - 1
misago/users/api/usernamechanges.py

@@ -26,7 +26,7 @@ class UsernameChangesViewSetPermission(BasePermission):
 
 
         if user_pk == request.user.pk:
         if user_pk == request.user.pk:
             return True
             return True
-        elif not request.user.acl_cache.get('can_see_users_name_history'):
+        elif not request.user_acl.get('can_see_users_name_history'):
             raise PermissionDenied(_("You don't have permission to see other users name history."))
             raise PermissionDenied(_("You don't have permission to see other users name history."))
         return True
         return True
 
 

+ 9 - 9
misago/users/api/users.py

@@ -75,7 +75,7 @@ class UserViewSet(viewsets.GenericViewSet):
         return user
         return user
 
 
     def list(self, request):
     def list(self, request):
-        allow_browse_users_list(request.user)
+        allow_browse_users_list(request.user_acl)
         return list_endpoint(request)
         return list_endpoint(request)
 
 
     def create(self, request):
     def create(self, request):
@@ -84,10 +84,10 @@ class UserViewSet(viewsets.GenericViewSet):
     def retrieve(self, request, pk=None):
     def retrieve(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
 
 
-        add_acl(request.user, profile)
+        add_acl(request.user_acl, profile)
         profile.status = get_user_status(request, profile)
         profile.status = get_user_status(request, profile)
 
 
-        serializer = UserProfileSerializer(profile, context={'user': request.user})
+        serializer = UserProfileSerializer(profile, context={'request': request})
         profile_json = serializer.data
         profile_json = serializer.data
 
 
         if not profile.is_active:
         if not profile.is_active:
@@ -153,7 +153,7 @@ class UserViewSet(viewsets.GenericViewSet):
     @detail_route(methods=['get', 'post'])
     @detail_route(methods=['get', 'post'])
     def edit_details(self, request, pk=None):
     def edit_details(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_edit_profile_details(request.user, profile)
+        allow_edit_profile_details(request.user_acl, profile)
         return edit_details_endpoint(request, profile)
         return edit_details_endpoint(request, profile)
 
 
     @detail_route(methods=['post'])
     @detail_route(methods=['post'])
@@ -169,7 +169,7 @@ class UserViewSet(viewsets.GenericViewSet):
     @detail_route(methods=['post'])
     @detail_route(methods=['post'])
     def follow(self, request, pk=None):
     def follow(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_follow_user(request.user, profile)
+        allow_follow_user(request.user_acl, profile)
 
 
         profile_followers = profile.followers
         profile_followers = profile.followers
 
 
@@ -197,7 +197,7 @@ class UserViewSet(viewsets.GenericViewSet):
     @detail_route()
     @detail_route()
     def ban(self, request, pk=None):
     def ban(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_see_ban_details(request.user, profile)
+        allow_see_ban_details(request.user_acl, profile)
 
 
         ban = get_user_ban(profile, request.cache_versions)
         ban = get_user_ban(profile, request.cache_versions)
         if ban:
         if ban:
@@ -208,14 +208,14 @@ class UserViewSet(viewsets.GenericViewSet):
     @detail_route(methods=['get', 'post'])
     @detail_route(methods=['get', 'post'])
     def moderate_avatar(self, request, pk=None):
     def moderate_avatar(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_moderate_avatar(request.user, profile)
+        allow_moderate_avatar(request.user_acl, profile)
 
 
         return moderate_avatar_endpoint(request, profile)
         return moderate_avatar_endpoint(request, profile)
 
 
     @detail_route(methods=['get', 'post'])
     @detail_route(methods=['get', 'post'])
     def moderate_username(self, request, pk=None):
     def moderate_username(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_rename_user(request.user, profile)
+        allow_rename_user(request.user_acl, profile)
 
 
         return moderate_username_endpoint(request, profile)
         return moderate_username_endpoint(request, profile)
 
 
@@ -238,7 +238,7 @@ class UserViewSet(viewsets.GenericViewSet):
     @detail_route(methods=['get', 'post'])
     @detail_route(methods=['get', 'post'])
     def delete(self, request, pk=None):
     def delete(self, request, pk=None):
         profile = self.get_user(request, pk)
         profile = self.get_user(request, pk)
-        allow_delete_user(request.user, profile)
+        allow_delete_user(request.user_acl, profile)
 
 
         if request.method == 'POST':
         if request.method == 'POST':
             with transaction.atomic():
             with transaction.atomic():

+ 2 - 2
misago/users/apps.py

@@ -71,14 +71,14 @@ class MisagoUsersConfig(AppConfig):
         def can_see_names_history(request, profile):
         def can_see_names_history(request, profile):
             if request.user.is_authenticated:
             if request.user.is_authenticated:
                 is_account_owner = profile.pk == request.user.pk
                 is_account_owner = profile.pk == request.user.pk
-                has_permission = request.user.acl_cache['can_see_users_name_history']
+                has_permission = request.user_acl['can_see_users_name_history']
                 return is_account_owner or has_permission
                 return is_account_owner or has_permission
             else:
             else:
                 return False
                 return False
 
 
         def can_see_ban_details(request, profile):
         def can_see_ban_details(request, profile):
             if request.user.is_authenticated:
             if request.user.is_authenticated:
-                if request.user.acl_cache['can_see_ban_details']:
+                if request.user_acl['can_see_ban_details']:
                     from .bans import get_user_ban
                     from .bans import get_user_ban
                     return bool(get_user_ban(profile, request.cache_versions))
                     return bool(get_user_ban(profile, request.cache_versions))
                 else:
                 else:

+ 14 - 4
misago/users/models/user.py

@@ -453,16 +453,26 @@ class User(AbstractBaseUser, PermissionsMixin):
         """sends an email to this user (for compat with Django)"""
         """sends an email to this user (for compat with Django)"""
         send_mail(subject, message, from_email, [self.email], **kwargs)
         send_mail(subject, message, from_email, [self.email], **kwargs)
 
 
-    def is_following(self, user):
+    def is_following(self, user_or_id):
         try:
         try:
-            self.follows.get(pk=user.pk)
+            user_id = user_or_id.id
+        except AttributeError:
+            user_id = user_or_id
+
+        try:
+            self.follows.get(id=user_id)
             return True
             return True
         except User.DoesNotExist:
         except User.DoesNotExist:
             return False
             return False
 
 
-    def is_blocking(self, user):
+    def is_blocking(self, user_or_id):
+        try:
+            user_id = user_or_id.id
+        except AttributeError:
+            user_id = user_or_id
+
         try:
         try:
-            self.blocks.get(pk=user.pk)
+            self.blocks.get(id=user_id)
             return True
             return True
         except User.DoesNotExist:
         except User.DoesNotExist:
             return False
             return False

+ 2 - 2
misago/users/online/utils.py

@@ -48,7 +48,7 @@ def get_user_status(request, user):
 
 
     try:
     try:
         online_tracker = user.online_tracker
         online_tracker = user.online_tracker
-        is_hidden = user.is_hiding_presence and not request.user.acl_cache['can_see_hidden_users']
+        is_hidden = user.is_hiding_presence and not request.user_acl['can_see_hidden_users']
 
 
         if online_tracker and not is_hidden:
         if online_tracker and not is_hidden:
             if online_tracker.last_click >= timezone.now() - ACTIVITY_CUTOFF:
             if online_tracker.last_click >= timezone.now() - ACTIVITY_CUTOFF:
@@ -58,7 +58,7 @@ def get_user_status(request, user):
         pass
         pass
 
 
     if user_status['is_hidden']:
     if user_status['is_hidden']:
-        if request.user.acl_cache['can_see_hidden_users']:
+        if request.user_acl['can_see_hidden_users']:
             user_status['is_hidden'] = False
             user_status['is_hidden'] = False
             if user_status['is_online']:
             if user_status['is_online']:
                 user_status['is_online_hidden'] = True
                 user_status['is_online_hidden'] = True

+ 13 - 11
misago/users/permissions/decorators.py

@@ -9,22 +9,24 @@ __all__ = [
 
 
 
 
 def authenticated_only(f):
 def authenticated_only(f):
-    def perm_decorator(user, target):
-        if user.is_authenticated:
-            return f(user, target)
-        else:
-            messsage = _("You have to sig in to perform this action.")
-            raise PermissionDenied(messsage)
+    def perm_decorator(user_acl, target):
+        if user_acl["is_authenticated"]:
+            return f(user_acl, target)
+        else: 
+            raise PermissionDenied(
+                _("You have to sig in to perform this action.")
+            )
 
 
     return perm_decorator
     return perm_decorator
 
 
 
 
 def anonymous_only(f):
 def anonymous_only(f):
-    def perm_decorator(user, target):
-        if user.is_anonymous:
-            return f(user, target)
+    def perm_decorator(user_acl, target):
+        if user_acl["is_anonymous"]:
+            return f(user_acl, target)
         else:
         else:
-            messsage = _("Only guests can perform this action.")
-            raise PermissionDenied(messsage)
+            raise PermissionDenied(
+                _("Only guests can perform this action.")
+            )
 
 
     return perm_decorator
     return perm_decorator

+ 7 - 7
misago/users/permissions/delete.py

@@ -61,8 +61,8 @@ def build_acl(acl, roles, key_name):
     )
     )
 
 
 
 
-def add_acl_to_user(user, target):
-    target.acl['can_delete'] = can_delete_user(user, target)
+def add_acl_to_user(user_acl, target):
+    target.acl['can_delete'] = can_delete_user(user_acl, target)
     if target.acl['can_delete']:
     if target.acl['can_delete']:
         target.acl['can_moderate'] = True
         target.acl['can_moderate'] = True
 
 
@@ -71,13 +71,13 @@ def register_with(registry):
     registry.acl_annotator(get_user_model(), add_acl_to_user)
     registry.acl_annotator(get_user_model(), add_acl_to_user)
 
 
 
 
-def allow_delete_user(user, target):
-    newer_than = user.acl_cache['can_delete_users_newer_than']
-    less_posts_than = user.acl_cache['can_delete_users_with_less_posts_than']
+def allow_delete_user(user_acl, target):
+    newer_than = user_acl['can_delete_users_newer_than']
+    less_posts_than = user_acl['can_delete_users_with_less_posts_than']
     if not newer_than and not less_posts_than:
     if not newer_than and not less_posts_than:
         raise PermissionDenied(_("You can't delete users."))
         raise PermissionDenied(_("You can't delete users."))
 
 
-    if user.pk == target.pk:
+    if user_acl["user_id"] == target.id:
         raise PermissionDenied(_("You can't delete your account."))
         raise PermissionDenied(_("You can't delete your account."))
     if target.is_staff or target.is_superuser:
     if target.is_staff or target.is_superuser:
         raise PermissionDenied(_("You can't delete administrators."))
         raise PermissionDenied(_("You can't delete administrators."))
@@ -106,7 +106,7 @@ can_delete_user = return_boolean(allow_delete_user)
 def allow_delete_own_account(user, target):
 def allow_delete_own_account(user, target):
     if not settings.MISAGO_ENABLE_DELETE_OWN_ACCOUNT and not user.is_deleting_account:
     if not settings.MISAGO_ENABLE_DELETE_OWN_ACCOUNT and not user.is_deleting_account:
         raise PermissionDenied(_("You can't delete your account."))
         raise PermissionDenied(_("You can't delete your account."))
-    if user.pk != target.pk:
+    if user.id != target.id:
         raise PermissionDenied(_("You can't delete other users accounts."))
         raise PermissionDenied(_("You can't delete other users accounts."))
     if user.is_staff or user.is_superuser:
     if user.is_staff or user.is_superuser:
         raise PermissionDenied(
         raise PermissionDenied(

+ 28 - 27
misago/users/permissions/moderation.py

@@ -88,14 +88,14 @@ def build_acl(acl, roles, key_name):
     )
     )
 
 
 
 
-def add_acl_to_user(user, target):
-    target.acl['can_rename'] = can_rename_user(user, target)
-    target.acl['can_moderate_avatar'] = can_moderate_avatar(user, target)
-    target.acl['can_moderate_signature'] = can_moderate_signature(user, target)
-    target.acl['can_edit_profile_details'] = can_edit_profile_details(user, target)
-    target.acl['can_ban'] = can_ban_user(user, target)
-    target.acl['max_ban_length'] = user.acl_cache['max_ban_length']
-    target.acl['can_lift_ban'] = can_lift_ban(user, target)
+def add_acl_to_user(user_acl, target):
+    target.acl['can_rename'] = can_rename_user(user_acl, target)
+    target.acl['can_moderate_avatar'] = can_moderate_avatar(user_acl, target)
+    target.acl['can_moderate_signature'] = can_moderate_signature(user_acl, target)
+    target.acl['can_edit_profile_details'] = can_edit_profile_details(user_acl, target)
+    target.acl['can_ban'] = can_ban_user(user_acl, target)
+    target.acl['max_ban_length'] = user_acl['max_ban_length']
+    target.acl['can_lift_ban'] = can_lift_ban(user_acl, target)
 
 
     mod_permissions = [
     mod_permissions = [
         'can_rename',
         'can_rename',
@@ -113,30 +113,30 @@ def register_with(registry):
     registry.acl_annotator(get_user_model(), add_acl_to_user)
     registry.acl_annotator(get_user_model(), add_acl_to_user)
 
 
 
 
-def allow_rename_user(user, target):
-    if not user.acl_cache['can_rename_users']:
+def allow_rename_user(user_acl, target):
+    if not user_acl['can_rename_users']:
         raise PermissionDenied(_("You can't rename users."))
         raise PermissionDenied(_("You can't rename users."))
-    if not user.is_superuser and (target.is_staff or target.is_superuser):
+    if not user_acl["is_superuser"] and (target.is_staff or target.is_superuser):
         raise PermissionDenied(_("You can't rename administrators."))
         raise PermissionDenied(_("You can't rename administrators."))
 
 
 
 
 can_rename_user = return_boolean(allow_rename_user)
 can_rename_user = return_boolean(allow_rename_user)
 
 
 
 
-def allow_moderate_avatar(user, target):
-    if not user.acl_cache['can_moderate_avatars']:
+def allow_moderate_avatar(user_acl, target):
+    if not user_acl['can_moderate_avatars']:
         raise PermissionDenied(_("You can't moderate avatars."))
         raise PermissionDenied(_("You can't moderate avatars."))
-    if not user.is_superuser and (target.is_staff or target.is_superuser):
+    if not user_acl["is_superuser"] and (target.is_staff or target.is_superuser):
         raise PermissionDenied(_("You can't moderate administrators avatars."))
         raise PermissionDenied(_("You can't moderate administrators avatars."))
 
 
 
 
 can_moderate_avatar = return_boolean(allow_moderate_avatar)
 can_moderate_avatar = return_boolean(allow_moderate_avatar)
 
 
 
 
-def allow_moderate_signature(user, target):
-    if not user.acl_cache['can_moderate_signatures']:
+def allow_moderate_signature(user_acl, target):
+    if not user_acl['can_moderate_signatures']:
         raise PermissionDenied(_("You can't moderate signatures."))
         raise PermissionDenied(_("You can't moderate signatures."))
-    if not user.is_superuser and (target.is_staff or target.is_superuser):
+    if not user_acl["is_superuser"] and (target.is_staff or target.is_superuser):
         message = _("You can't moderate administrators signatures.")
         message = _("You can't moderate administrators signatures.")
         raise PermissionDenied(message)
         raise PermissionDenied(message)
 
 
@@ -144,12 +144,12 @@ def allow_moderate_signature(user, target):
 can_moderate_signature = return_boolean(allow_moderate_signature)
 can_moderate_signature = return_boolean(allow_moderate_signature)
 
 
 
 
-def allow_edit_profile_details(user, target):
-    if user.is_anonymous:
+def allow_edit_profile_details(user_acl, target):
+    if user_acl["is_anonymous"]:
         raise PermissionDenied(_("You have to sign in to edit profile details."))
         raise PermissionDenied(_("You have to sign in to edit profile details."))
-    if user != target and not user.acl_cache['can_moderate_profile_details']:
+    if user_acl["user_id"] != target.id and not user_acl['can_moderate_profile_details']:
         raise PermissionDenied(_("You can't edit other users details."))
         raise PermissionDenied(_("You can't edit other users details."))
-    if not user.is_superuser and (target.is_staff or target.is_superuser):
+    if not user_acl["is_superuser"] and (target.is_staff or target.is_superuser):
         message = _("You can't edit administrators details.")
         message = _("You can't edit administrators details.")
         raise PermissionDenied(message)
         raise PermissionDenied(message)
 
 
@@ -157,8 +157,8 @@ def allow_edit_profile_details(user, target):
 can_edit_profile_details = return_boolean(allow_edit_profile_details)
 can_edit_profile_details = return_boolean(allow_edit_profile_details)
 
 
 
 
-def allow_ban_user(user, target):
-    if not user.acl_cache['can_ban_users']:
+def allow_ban_user(user_acl, target):
+    if not user_acl['can_ban_users']:
         raise PermissionDenied(_("You can't ban users."))
         raise PermissionDenied(_("You can't ban users."))
     if target.is_staff or target.is_superuser:
     if target.is_staff or target.is_superuser:
         raise PermissionDenied(_("You can't ban administrators."))
         raise PermissionDenied(_("You can't ban administrators."))
@@ -167,14 +167,15 @@ def allow_ban_user(user, target):
 can_ban_user = return_boolean(allow_ban_user)
 can_ban_user = return_boolean(allow_ban_user)
 
 
 
 
-def allow_lift_ban(user, target):
-    if not user.acl_cache['can_lift_bans']:
+def allow_lift_ban(user_acl, target):
+    if not user_acl['can_lift_bans']:
         raise PermissionDenied(_("You can't lift bans."))
         raise PermissionDenied(_("You can't lift bans."))
+    # FIXME: this will require cache version delegation
     ban = get_user_ban(target)
     ban = get_user_ban(target)
     if not ban:
     if not ban:
         raise PermissionDenied(_("This user is not banned."))
         raise PermissionDenied(_("This user is not banned."))
-    if user.acl_cache['max_lifted_ban_length']:
-        expiration_limit = timedelta(days=user.acl_cache['max_lifted_ban_length'])
+    if user_acl['max_lifted_ban_length']:
+        expiration_limit = timedelta(days=user_acl['max_lifted_ban_length'])
         lift_cutoff = (timezone.now() + expiration_limit).date()
         lift_cutoff = (timezone.now() + expiration_limit).date()
         if not ban.valid_until:
         if not ban.valid_until:
             raise PermissionDenied(_("You can't lift permanent bans."))
             raise PermissionDenied(_("You can't lift permanent bans."))

+ 13 - 12
misago/users/permissions/profiles.py

@@ -92,10 +92,10 @@ def build_acl(acl, roles, key_name):
     )
     )
 
 
 
 
-def add_acl_to_user(user, target):
+def add_acl_to_user(user_acl, target):
     target.acl['can_have_attitude'] = False
     target.acl['can_have_attitude'] = False
-    target.acl['can_follow'] = can_follow_user(user, target)
-    target.acl['can_block'] = can_block_user(user, target)
+    target.acl['can_follow'] = can_follow_user(user_acl, target)
+    target.acl['can_block'] = can_block_user(user_acl, target)
 
 
     mod_permissions = ('can_have_attitude', 'can_follow', 'can_block', )
     mod_permissions = ('can_have_attitude', 'can_follow', 'can_block', )
 
 
@@ -109,8 +109,8 @@ def register_with(registry):
     registry.acl_annotator(get_user_model(), add_acl_to_user)
     registry.acl_annotator(get_user_model(), add_acl_to_user)
 
 
 
 
-def allow_browse_users_list(user):
-    if not user.acl_cache['can_browse_users_list']:
+def allow_browse_users_list(user_acl):
+    if not user_acl['can_browse_users_list']:
         raise PermissionDenied(_("You can't browse users list."))
         raise PermissionDenied(_("You can't browse users list."))
 
 
 
 
@@ -118,10 +118,10 @@ can_browse_users_list = return_boolean(allow_browse_users_list)
 
 
 
 
 @authenticated_only
 @authenticated_only
-def allow_follow_user(user, target):
-    if not user.acl_cache['can_follow_users']:
+def allow_follow_user(user_acl, target):
+    if not user_acl['can_follow_users']:
         raise PermissionDenied(_("You can't follow other users."))
         raise PermissionDenied(_("You can't follow other users."))
-    if user.pk == target.pk:
+    if user_acl["user_id"] == target.id:
         raise PermissionDenied(_("You can't add yourself to followed."))
         raise PermissionDenied(_("You can't add yourself to followed."))
 
 
 
 
@@ -129,11 +129,12 @@ can_follow_user = return_boolean(allow_follow_user)
 
 
 
 
 @authenticated_only
 @authenticated_only
-def allow_block_user(user, target):
+def allow_block_user(user_acl, target):
     if target.is_staff or target.is_superuser:
     if target.is_staff or target.is_superuser:
         raise PermissionDenied(_("You can't block administrators."))
         raise PermissionDenied(_("You can't block administrators."))
-    if user.pk == target.pk:
+    if user_acl["user_id"] == target.id:
         raise PermissionDenied(_("You can't block yourself."))
         raise PermissionDenied(_("You can't block yourself."))
+    # FIXME: this will require changes in ACL checking
     if not target.acl_cache['can_be_blocked'] or target.is_superuser:
     if not target.acl_cache['can_be_blocked'] or target.is_superuser:
         message = _("%(user)s can't be blocked.") % {'user': target.username}
         message = _("%(user)s can't be blocked.") % {'user': target.username}
         raise PermissionDenied(message)
         raise PermissionDenied(message)
@@ -143,8 +144,8 @@ can_block_user = return_boolean(allow_block_user)
 
 
 
 
 @authenticated_only
 @authenticated_only
-def allow_see_ban_details(user, target):
-    if not user.acl_cache['can_see_ban_details']:
+def allow_see_ban_details(user_acl, target):
+    if not user_acl['can_see_ban_details']:
         raise PermissionDenied(_("You can't see users bans details."))
         raise PermissionDenied(_("You can't see users bans details."))
 
 
 
 

+ 1 - 1
misago/users/profilefields/default.py

@@ -84,7 +84,7 @@ class JoinIpField(basefields.TextProfileField):
     readonly = True
     readonly = True
 
 
     def get_value_display_data(self, request, user, value):
     def get_value_display_data(self, request, user, value):
-        if not request.user.acl_cache.get('can_see_users_ips'):
+        if not request.user_acl.get('can_see_users_ips'):
             return None
             return None
 
 
         if not user.joined_from_ip:
         if not user.joined_from_ip:

+ 1 - 1
misago/users/profilefields/serializers.py

@@ -8,7 +8,7 @@ def serialize_profilefields_data(request, profilefields, user):
         'edit': False,
         'edit': False,
     }
     }
 
 
-    can_edit = can_edit_profile_details(request.user, user)
+    can_edit = can_edit_profile_details(request.user_acl, user)
     has_editable_fields = False
     has_editable_fields = False
 
 
     for group in profilefields.get_fields_groups():
     for group in profilefields.get_fields_groups():

+ 1 - 1
misago/users/search.py

@@ -20,7 +20,7 @@ class SearchUsers(SearchProvider):
     url = 'users'
     url = 'users'
 
 
     def allow_search(self):
     def allow_search(self):
-        if not self.request.user.acl_cache['can_search_users']:
+        if not self.request.user_acl['can_search_users']:
             raise PermissionDenied(_("You don't have permission to search users."))
             raise PermissionDenied(_("You don't have permission to search users."))
 
 
     def search(self, query, page=1):
     def search(self, query, page=1):

+ 6 - 3
misago/users/serializers/user.py

@@ -74,20 +74,23 @@ class UserSerializer(serializers.ModelSerializer, MutableFields):
         return obj.acl
         return obj.acl
 
 
     def get_email(self, obj):
     def get_email(self, obj):
-        if (obj == self.context['user'] or self.context['user'].acl_cache['can_see_users_emails']):
+        request = self.context['request']
+        if (obj == request.user or request.user_acl['can_see_users_emails']):
             return obj.email
             return obj.email
         else:
         else:
             return None
             return None
 
 
     def get_is_followed(self, obj):
     def get_is_followed(self, obj):
+        request = self.context['request']
         if obj.acl['can_follow']:
         if obj.acl['can_follow']:
-            return self.context['user'].is_following(obj)
+            return request.user.is_following(obj)
         else:
         else:
             return False
             return False
 
 
     def get_is_blocked(self, obj):
     def get_is_blocked(self, obj):
+        request = self.context['request']
         if obj.acl['can_block']:
         if obj.acl['can_block']:
-            return self.context['user'].is_blocking(obj)
+            return request.user.is_blocking(obj)
         else:
         else:
             return False
             return False
 
 

+ 7 - 5
misago/users/tests/test_joinip_profilefield.py

@@ -1,8 +1,8 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
+from misago.acl.test import patch_user_acl
 from misago.admin.testutils import AdminTestCase
 from misago.admin.testutils import AdminTestCase
-from misago.acl.testutils import override_acl
 
 
 
 
 UserModel = get_user_model()
 UserModel = get_user_model()
@@ -74,7 +74,8 @@ class JoinIpProfileFieldTests(AdminTestCase):
         self.assertContains(response, "Join IP")
         self.assertContains(response, "Join IP")
         self.assertContains(response, "127.0.0.1")
         self.assertContains(response, "127.0.0.1")
 
 
-    def test_field_hidden_no_permission(self):
+    @patch_user_acl
+    def test_field_hidden_no_permission(self, patch_user_acl):
         """field is hidden on user profile if user has no permission"""
         """field is hidden on user profile if user has no permission"""
         test_link = reverse(
         test_link = reverse(
             'misago:user-details',
             'misago:user-details',
@@ -84,7 +85,7 @@ class JoinIpProfileFieldTests(AdminTestCase):
             },
             },
         )
         )
 
 
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_see_users_ips': 0
             'can_see_users_ips': 0
         })
         })
 
 
@@ -132,11 +133,12 @@ class JoinIpProfileFieldTests(AdminTestCase):
             ]
             ]
         )
         )
 
 
-    def test_field_hidden_no_permission_json(self):
+    @patch_user_acl
+    def test_field_hidden_no_permission_json(self, patch_user_acl):
         """field is not included in display json if user has no permission"""
         """field is not included in display json if user has no permission"""
         test_link = reverse('misago:api:user-details', kwargs={'pk': self.user.pk})
         test_link = reverse('misago:api:user-details', kwargs={'pk': self.user.pk})
 
 
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_see_users_ips': 0
             'can_see_users_ips': 0
         })
         })
 
 

+ 5 - 8
misago/users/tests/test_lists_views.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads.testutils import post_thread
 from misago.threads.testutils import post_thread
 from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.activepostersranking import build_active_posters_ranking
@@ -13,17 +13,14 @@ UserModel = get_user_model()
 
 
 
 
 class UsersListTestCase(AuthenticatedUserTestCase):
 class UsersListTestCase(AuthenticatedUserTestCase):
-    def setUp(self):
-        super().setUp()
-        override_acl(self.user, {
-            'can_browse_users_list': 1,
-        })
+    pass
 
 
 
 
 class UsersListLanderTests(UsersListTestCase):
 class UsersListLanderTests(UsersListTestCase):
-    def test_lander_no_permission(self):
+    @patch_user_acl
+    def test_lander_no_permission(self, patch_user_acl):
         """lander returns 403 if user has no permission"""
         """lander returns 403 if user has no permission"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_browse_users_list': 0,
             'can_browse_users_list': 0,
         })
         })
 
 

+ 17 - 11
misago/users/tests/test_online_utils.py

@@ -2,25 +2,27 @@ from unittest.mock import Mock
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 
 
-from misago.acl.testutils import override_acl
 from misago.users.online.utils import get_user_status
 from misago.users.online.utils import get_user_status
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
-
-UserModel = get_user_model()
+User = get_user_model()
 
 
 
 
 class GetUserStatusTests(AuthenticatedUserTestCase):
 class GetUserStatusTests(AuthenticatedUserTestCase):
     def setUp(self):
     def setUp(self):
         super().setUp()
         super().setUp()
-        self.other_user = UserModel.objects.create_user('Tyrael', 't123@test.com', 'pass123')
+        self.other_user = User.objects.create_user('Tyrael', 't123@test.com', 'pass123')
 
 
     def test_user_hiding_presence(self):
     def test_user_hiding_presence(self):
         """get_user_status has no showstopper for hidden user"""
         """get_user_status has no showstopper for hidden user"""
         self.other_user.is_hiding_presence = True
         self.other_user.is_hiding_presence = True
         self.other_user.save()
         self.other_user.save()
 
 
-        request = Mock(user=self.user, cache_versions={"bans": "abcdfghi"})
+        request = Mock(
+            user=self.user,
+            user_acl={'can_see_hidden_users': False},
+            cache_versions={"bans": "abcdefgh"},
+        )
         get_user_status(request, self.other_user)
         get_user_status(request, self.other_user)
 
 
     def test_user_visible_hidden_presence(self):
     def test_user_visible_hidden_presence(self):
@@ -28,14 +30,18 @@ class GetUserStatusTests(AuthenticatedUserTestCase):
         self.other_user.is_hiding_presence = True
         self.other_user.is_hiding_presence = True
         self.other_user.save()
         self.other_user.save()
 
 
-        override_acl(self.user, {
-            'can_see_hidden_users': True,
-        })
-
-        request = Mock(user=self.user, cache_versions={"bans": "abcdfghi"})
+        request = Mock(
+            user=self.user,
+            user_acl={'can_see_hidden_users': True},
+            cache_versions={"bans": "abcdefgh"},
+        )
         get_user_status(request, self.other_user)
         get_user_status(request, self.other_user)
 
 
     def test_user_not_hiding_presence(self):
     def test_user_not_hiding_presence(self):
         """get_user_status has no showstoppers for non-hidden user"""
         """get_user_status has no showstoppers for non-hidden user"""
-        request = Mock(user=self.user, cache_versions={"bans": "abcdfghi"})
+        request = Mock(
+            user=self.user,
+            user_acl={'can_see_hidden_users': False},
+            cache_versions={"bans": "abcdefgh"},
+        )
         get_user_status(request, self.other_user)
         get_user_status(request, self.other_user)

+ 5 - 7
misago/users/tests/test_profile_views.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.users.models import Ban
 from misago.users.models import Ban
@@ -182,9 +182,10 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.assertContains(response, "TestUser")
         self.assertContains(response, "TestUser")
         self.assertContains(response, "RenamedAdmin")
         self.assertContains(response, "RenamedAdmin")
 
 
-    def test_user_ban_details(self):
+    @patch_user_acl
+    def test_user_ban_details(self, patch_user_acl):
         """user ban details page has no showstoppers"""
         """user ban details page has no showstoppers"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_see_ban_details': 0,
             'can_see_ban_details': 0,
         })
         })
 
 
@@ -197,7 +198,7 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         ))
         ))
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_see_ban_details': 1,
             'can_see_ban_details': 1,
         })
         })
 
 
@@ -207,9 +208,6 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         ))
         ))
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
-        override_acl(self.user, {
-            'can_see_ban_details': 1,
-        })
         test_user.ban_cache.delete()
         test_user.ban_cache.delete()
 
 
         Ban.objects.create(
         Ban.objects.create(

+ 4 - 3
misago/users/tests/test_search.py

@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -14,9 +14,10 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
 
         self.api_link = reverse('misago:api:search')
         self.api_link = reverse('misago:api:search')
 
 
-    def test_no_permission(self):
+    @patch_user_acl
+    def test_no_permission(self, patch_user_acl):
         """api respects permission to search users"""
         """api respects permission to search users"""
-        override_acl(self.user, {'can_search_users': 0})
+        patch_user_acl(self.user, {'can_search_users': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 10 - 23
misago/users/tests/test_user_avatar_api.py

@@ -4,7 +4,7 @@ from pathlib import Path
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.conf import settings
 from misago.conf import settings
 from misago.users.avatars import gallery, store
 from misago.users.avatars import gallery, store
 from misago.users.models import AvatarGallery
 from misago.users.models import AvatarGallery
@@ -351,9 +351,10 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
 
 
         self.link = '/api/users/%s/moderate-avatar/' % self.other_user.pk
         self.link = '/api/users/%s/moderate-avatar/' % self.other_user.pk
 
 
-    def test_no_permission(self):
+    @patch_user_acl
+    def test_no_permission(self, patch_user_acl):
         """no permission to moderate avatar"""
         """no permission to moderate avatar"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_avatars': 0,
             'can_moderate_avatars': 0,
         })
         })
 
 
@@ -363,9 +364,10 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             "detail": "You can't moderate avatars.",
             "detail": "You can't moderate avatars.",
         })
         })
 
 
-    def test_moderate_avatar(self):
+    @patch_user_acl
+    def test_moderate_avatar(self, patch_user_acl):
         """moderate avatar"""
         """moderate avatar"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_avatars': 1,
             'can_moderate_avatars': 1,
         })
         })
 
 
@@ -381,10 +383,6 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             options['avatar_lock_staff_message'], self.other_user.avatar_lock_staff_message
             options['avatar_lock_staff_message'], self.other_user.avatar_lock_staff_message
         )
         )
 
 
-        override_acl(self.user, {
-            'can_moderate_avatars': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -410,10 +408,6 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
         )
         )
 
 
-        override_acl(self.user, {
-            'can_moderate_avatars': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -438,10 +432,6 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
         )
         )
 
 
-        override_acl(self.user, {
-            'can_moderate_avatars': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -466,10 +456,6 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
         )
         )
 
 
-        override_acl(self.user, {
-            'can_moderate_avatars': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -492,9 +478,10 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
             options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
         )
         )
 
 
-    def test_moderate_own_avatar(self):
+    @patch_user_acl
+    def test_moderate_own_avatar(self, patch_user_acl):
         """moderate own avatar"""
         """moderate own avatar"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_avatars': 1,
             'can_moderate_avatars': 1,
         })
         })
 
 

+ 5 - 5
misago/users/tests/test_user_details_api.py

@@ -1,8 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
-
+from misago.acl.test import patch_user_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -44,7 +43,8 @@ class UserDetailsApiTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertTrue(response.json()['edit'])
         self.assertTrue(response.json()['edit'])
 
 
-    def test_other_user(self):
+    @patch_user_acl
+    def test_other_user(self, patch_user_acl):
         """api handles scenario when its other user looking at profile"""
         """api handles scenario when its other user looking at profile"""
         test_user = UserModel.objects.create_user('BobBoberson', 'bob@test.com', 'bob123456')
         test_user = UserModel.objects.create_user('BobBoberson', 'bob@test.com', 'bob123456')
 
 
@@ -56,7 +56,7 @@ class UserDetailsApiTests(AuthenticatedUserTestCase):
         )
         )
 
 
         # moderator has permission to edit details
         # moderator has permission to edit details
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_profile_details': True,
             'can_moderate_profile_details': True,
         })
         })
 
 
@@ -65,7 +65,7 @@ class UserDetailsApiTests(AuthenticatedUserTestCase):
         self.assertTrue(response.json()['edit'])
         self.assertTrue(response.json()['edit'])
 
 
         # non-moderator has no permission to edit details
         # non-moderator has no permission to edit details
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_profile_details': False,
             'can_moderate_profile_details': False,
         })
         })
 
 

+ 5 - 5
misago/users/tests/test_user_editdetails_api.py

@@ -1,8 +1,7 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
-
+from misago.acl.test import patch_user_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -33,7 +32,8 @@ class UserEditDetailsApiTests(AuthenticatedUserTestCase):
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-    def test_other_user(self):
+    @patch_user_acl
+    def test_other_user(self, patch_user_acl):
         """api handles scenario when its other user looking at profile"""
         """api handles scenario when its other user looking at profile"""
         test_user = UserModel.objects.create_user('BobBoberson', 'bob@test.com', 'bob123456')
         test_user = UserModel.objects.create_user('BobBoberson', 'bob@test.com', 'bob123456')
 
 
@@ -45,7 +45,7 @@ class UserEditDetailsApiTests(AuthenticatedUserTestCase):
         )
         )
 
 
         # moderator has permission to edit details
         # moderator has permission to edit details
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_profile_details': True,
             'can_moderate_profile_details': True,
         })
         })
 
 
@@ -53,7 +53,7 @@ class UserEditDetailsApiTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # non-moderator has no permission to edit details
         # non-moderator has no permission to edit details
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_moderate_profile_details': False,
             'can_moderate_profile_details': False,
         })
         })
 
 

+ 19 - 13
misago/users/tests/test_user_signature_api.py

@@ -1,4 +1,4 @@
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -9,9 +9,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         super().setUp()
         super().setUp()
         self.link = '/api/users/%s/signature/' % self.user.pk
         self.link = '/api/users/%s/signature/' % self.user.pk
 
 
-    def test_signature_no_permission(self):
+    @patch_user_acl
+    def test_signature_no_permission(self, patch_user_acl):
         """edit signature api with no ACL returns 403"""
         """edit signature api with no ACL returns 403"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 0,
             'can_have_signature': 0,
         })
         })
 
 
@@ -21,9 +22,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
             "detail": "You don't have permission to change signature.",
             "detail": "You don't have permission to change signature.",
         })
         })
 
 
-    def test_signature_locked(self):
+    @patch_user_acl
+    def test_signature_locked(self, patch_user_acl):
         """locked edit signature returns 403"""
         """locked edit signature returns 403"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 1,
             'can_have_signature': 1,
         })
         })
 
 
@@ -38,9 +40,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
             "reason": "<p>Your siggy is banned.</p>",
             "reason": "<p>Your siggy is banned.</p>",
         })
         })
 
 
-    def test_get_signature(self):
+    @patch_user_acl
+    def test_get_signature(self, patch_user_acl):
         """GET to api returns json with no signature"""
         """GET to api returns json with no signature"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 1,
             'can_have_signature': 1,
         })
         })
 
 
@@ -52,9 +55,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
 
 
         self.assertFalse(response.json()['signature'])
         self.assertFalse(response.json()['signature'])
 
 
-    def test_post_empty_signature(self):
+    @patch_user_acl
+    def test_post_empty_signature(self, patch_user_acl):
         """empty POST empties user signature"""
         """empty POST empties user signature"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 1,
             'can_have_signature': 1,
         })
         })
 
 
@@ -71,9 +75,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
 
 
         self.assertFalse(response.json()['signature'])
         self.assertFalse(response.json()['signature'])
 
 
-    def test_post_too_long_signature(self):
+    @patch_user_acl
+    def test_post_too_long_signature(self, patch_user_acl):
         """too long new signature errors"""
         """too long new signature errors"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 1,
             'can_have_signature': 1,
         })
         })
 
 
@@ -91,9 +96,10 @@ class UserSignatureTests(AuthenticatedUserTestCase):
             "detail": "Signature is too long.",
             "detail": "Signature is too long.",
         })
         })
 
 
-    def test_post_good_signature(self):
+    @patch_user_acl
+    def test_post_good_signature(self, patch_user_acl):
         """POST with good signature changes user signature"""
         """POST with good signature changes user signature"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_have_signature': 1,
             'can_have_signature': 1,
         })
         })
 
 

+ 10 - 27
misago/users/tests/test_user_username_api.py

@@ -2,7 +2,7 @@ import json
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.conf import settings
 from misago.conf import settings
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
@@ -117,9 +117,10 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
 
 
         self.link = '/api/users/%s/moderate-username/' % self.other_user.pk
         self.link = '/api/users/%s/moderate-username/' % self.other_user.pk
 
 
-    def test_no_permission(self):
+    @patch_user_acl
+    def test_no_permission(self, patch_user_acl):
         """no permission to moderate avatar"""
         """no permission to moderate avatar"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_rename_users': 0,
             'can_rename_users': 0,
         })
         })
 
 
@@ -129,19 +130,16 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
             "detail": "You can't rename users.",
             "detail": "You can't rename users.",
         })
         })
 
 
-        override_acl(self.user, {
-            'can_rename_users': 0,
-        })
-
         response = self.client.post(self.link)
         response = self.client.post(self.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 rename users.",
             "detail": "You can't rename users.",
         })
         })
 
 
-    def test_moderate_username(self):
+    @patch_user_acl
+    def test_moderate_username(self, patch_user_acl):
         """moderate username"""
         """moderate username"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_rename_users': 1,
             'can_rename_users': 1,
         })
         })
 
 
@@ -152,10 +150,6 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['length_min'], settings.username_length_min)
         self.assertEqual(options['length_min'], settings.username_length_min)
         self.assertEqual(options['length_max'], settings.username_length_max)
         self.assertEqual(options['length_max'], settings.username_length_max)
 
 
-        override_acl(self.user, {
-            'can_rename_users': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -168,10 +162,6 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
             "detail": "Enter new username.",
             "detail": "Enter new username.",
         })
         })
 
 
-        override_acl(self.user, {
-            'can_rename_users': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -184,10 +174,6 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
             "detail": "Username can only contain latin alphabet letters and digits.",
             "detail": "Username can only contain latin alphabet letters and digits.",
         })
         })
 
 
-        override_acl(self.user, {
-            'can_rename_users': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -200,10 +186,6 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
             "detail": "Username must be at least 3 characters long.",
             "detail": "Username must be at least 3 characters long.",
         })
         })
 
 
-        override_acl(self.user, {
-            'can_rename_users': 1,
-        })
-
         response = self.client.post(
         response = self.client.post(
             self.link,
             self.link,
             json.dumps({
             json.dumps({
@@ -223,9 +205,10 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['username'], other_user.username)
         self.assertEqual(options['username'], other_user.username)
         self.assertEqual(options['slug'], other_user.slug)
         self.assertEqual(options['slug'], other_user.slug)
 
 
-    def test_moderate_own_username(self):
+    @patch_user_acl
+    def test_moderate_own_username(self, patch_user_acl):
         """moderate own username"""
         """moderate own username"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_rename_users': 1,
             'can_rename_users': 1,
         })
         })
 
 

+ 16 - 11
misago/users/tests/test_usernamechanges_api.py

@@ -1,4 +1,4 @@
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -7,39 +7,43 @@ class UsernameChangesApiTests(AuthenticatedUserTestCase):
         super().setUp()
         super().setUp()
         self.link = '/api/username-changes/'
         self.link = '/api/username-changes/'
 
 
-    def test_user_can_always_see_his_name_changes(self):
+    @patch_user_acl
+    def test_user_can_always_see_his_name_changes(self, patch_user_acl):
         """list returns own username changes"""
         """list returns own username changes"""
         self.user.set_username('NewUsername', self.user)
         self.user.set_username('NewUsername', self.user)
 
 
-        override_acl(self.user, {'can_see_users_name_history': False})
+        patch_user_acl(self.user, {'can_see_users_name_history': False})
 
 
         response = self.client.get('%s?user=%s' % (self.link, self.user.pk))
         response = self.client.get('%s?user=%s' % (self.link, self.user.pk))
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, self.user.username)
         self.assertContains(response, self.user.username)
 
 
-    def test_list_handles_invalid_filter(self):
+    @patch_user_acl
+    def test_list_handles_invalid_filter(self, patch_user_acl):
         """list raises 404 for invalid filter"""
         """list raises 404 for invalid filter"""
         self.user.set_username('NewUsername', self.user)
         self.user.set_username('NewUsername', self.user)
 
 
-        override_acl(self.user, {'can_see_users_name_history': True})
+        patch_user_acl(self.user, {'can_see_users_name_history': True})
 
 
         response = self.client.get('%s?user=abcd' % self.link)
         response = self.client.get('%s?user=abcd' % self.link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
-    def test_list_handles_nonexisting_user(self):
+    @patch_user_acl
+    def test_list_handles_nonexisting_user(self, patch_user_acl):
         """list raises 404 for invalid user id"""
         """list raises 404 for invalid user id"""
         self.user.set_username('NewUsername', self.user)
         self.user.set_username('NewUsername', self.user)
 
 
-        override_acl(self.user, {'can_see_users_name_history': True})
+        patch_user_acl(self.user, {'can_see_users_name_history': True})
 
 
         response = self.client.get('%s?user=142141' % self.link)
         response = self.client.get('%s?user=142141' % self.link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
-    def test_list_handles_search(self):
+    @patch_user_acl
+    def test_list_handles_search(self, patch_user_acl):
         """list returns found username changes"""
         """list returns found username changes"""
         self.user.set_username('NewUsername', self.user)
         self.user.set_username('NewUsername', self.user)
 
 
-        override_acl(self.user, {'can_see_users_name_history': False})
+        patch_user_acl(self.user, {'can_see_users_name_history': False})
 
 
         response = self.client.get('%s?user=%s&search=new' % (self.link, self.user.pk))
         response = self.client.get('%s?user=%s&search=new' % (self.link, self.user.pk))
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -49,9 +53,10 @@ class UsernameChangesApiTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.json()["count"], 0)
         self.assertEqual(response.json()["count"], 0)
 
 
-    def test_list_denies_permission(self):
+    @patch_user_acl
+    def test_list_denies_permission(self, patch_user_acl):
         """list denies permission for other user (or all) if no access"""
         """list denies permission for other user (or all) if no access"""
-        override_acl(self.user, {'can_see_users_name_history': False})
+        patch_user_acl(self.user, {'can_see_users_name_history': False})
 
 
         response = self.client.get('%s?user=%s' % (self.link, self.user.pk + 1))
         response = self.client.get('%s?user=%s' % (self.link, self.user.pk + 1))
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)

+ 37 - 25
misago/users/tests/test_users_api.py

@@ -6,7 +6,7 @@ from django.test import override_settings
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.encoding import smart_str
 from django.utils.encoding import smart_str
 
 
-from misago.acl.testutils import override_acl
+from misago.acl.test import patch_user_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.core import threadstore
 from misago.core import threadstore
 from misago.core.cache import cache
 from misago.core.cache import cache
@@ -421,9 +421,10 @@ class UserFollowTests(AuthenticatedUserTestCase):
             "detail": "You can't add yourself to followed.",
             "detail": "You can't add yourself to followed.",
         })
         })
 
 
-    def test_cant_follow(self):
+    @patch_user_acl
+    def test_cant_follow(self, patch_user_acl):
         """no permission to follow users"""
         """no permission to follow users"""
-        override_acl(self.user, {
+        patch_user_acl(self.user, {
             'can_follow_users': 0,
             'can_follow_users': 0,
         })
         })
 
 
@@ -476,9 +477,10 @@ class UserBanTests(AuthenticatedUserTestCase):
 
 
         self.link = '/api/users/%s/ban/' % self.other_user.pk
         self.link = '/api/users/%s/ban/' % self.other_user.pk
 
 
-    def test_no_permission(self):
+    @patch_user_acl
+    def test_no_permission(self, patch_user_acl):
         """user has no permission to access ban"""
         """user has no permission to access ban"""
-        override_acl(self.user, {'can_see_ban_details': 0})
+        patch_user_acl(self.user, {'can_see_ban_details': 0})
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
@@ -486,17 +488,19 @@ class UserBanTests(AuthenticatedUserTestCase):
             "detail": "You can't see users bans details.",
             "detail": "You can't see users bans details.",
         })
         })
 
 
-    def test_no_ban(self):
+    @patch_user_acl
+    def test_no_ban(self, patch_user_acl):
         """api returns empty json"""
         """api returns empty json"""
-        override_acl(self.user, {'can_see_ban_details': 1})
+        patch_user_acl(self.user, {'can_see_ban_details': 1})
 
 
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.json(), {})
         self.assertEqual(response.json(), {})
 
 
-    def test_ban_details(self):
+    @patch_user_acl
+    def test_ban_details(self, patch_user_acl):
         """api returns ban json"""
         """api returns ban json"""
-        override_acl(self.user, {'can_see_ban_details': 1})
+        patch_user_acl(self.user, {'can_see_ban_details': 1})
 
 
         Ban.objects.create(
         Ban.objects.create(
             check_type=Ban.USERNAME,
             check_type=Ban.USERNAME,
@@ -604,9 +608,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         self.other_user.threads = 1
         self.other_user.threads = 1
         self.other_user.save()
         self.other_user.save()
 
 
-    def test_delete_no_permission(self):
+    @patch_user_acl
+    def test_delete_no_permission(self, patch_user_acl):
         """raises 403 error when no permission to delete"""
         """raises 403 error when no permission to delete"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 0,
                 'can_delete_users_newer_than': 0,
                 'can_delete_users_with_less_posts_than': 0,
                 'can_delete_users_with_less_posts_than': 0,
@@ -619,9 +624,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete users.",
             'detail': "You can't delete users.",
         })
         })
 
 
-    def test_delete_too_many_posts(self):
+    @patch_user_acl
+    def test_delete_too_many_posts(self, patch_user_acl):
         """raises 403 error when user has too many posts"""
         """raises 403 error when user has too many posts"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 0,
                 'can_delete_users_newer_than': 0,
                 'can_delete_users_with_less_posts_than': 5,
                 'can_delete_users_with_less_posts_than': 5,
@@ -637,9 +643,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete users that made more than 5 posts.",
             'detail': "You can't delete users that made more than 5 posts.",
         })
         })
 
 
-    def test_delete_too_old_member(self):
+    @patch_user_acl
+    def test_delete_too_old_member(self, patch_user_acl):
         """raises 403 error when user is too old"""
         """raises 403 error when user is too old"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 5,
                 'can_delete_users_newer_than': 5,
                 'can_delete_users_with_less_posts_than': 0,
                 'can_delete_users_with_less_posts_than': 0,
@@ -656,9 +663,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete users that are members for more than 5 days.",
             'detail': "You can't delete users that are members for more than 5 days.",
         })
         })
 
 
-    def test_delete_self(self):
+    @patch_user_acl
+    def test_delete_self(self, patch_user_acl):
         """raises 403 error when attempting to delete oneself"""
         """raises 403 error when attempting to delete oneself"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
@@ -671,9 +679,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete your account.",
             'detail': "You can't delete your account.",
         })
         })
 
 
-    def test_delete_admin(self):
+    @patch_user_acl
+    def test_delete_admin(self, patch_user_acl):
         """raises 403 error when attempting to delete admin"""
         """raises 403 error when attempting to delete admin"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
@@ -689,9 +698,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete administrators.",
             'detail': "You can't delete administrators.",
         })
         })
 
 
-    def test_delete_superadmin(self):
+    @patch_user_acl
+    def test_delete_superadmin(self, patch_user_acl):
         """raises 403 error when attempting to delete superadmin"""
         """raises 403 error when attempting to delete superadmin"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
@@ -707,9 +717,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
             'detail': "You can't delete administrators.",
             'detail': "You can't delete administrators.",
         })
         })
 
 
-    def test_delete_with_content(self):
+    @patch_user_acl
+    def test_delete_with_content(self, patch_user_acl):
         """returns 200 and deletes user with content"""
         """returns 200 and deletes user with content"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
@@ -731,9 +742,10 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         self.assertEqual(Thread.objects.count(), self.threads)
         self.assertEqual(Thread.objects.count(), self.threads)
         self.assertEqual(Post.objects.count(), self.posts)
         self.assertEqual(Post.objects.count(), self.posts)
 
 
-    def test_delete_without_content(self):
+    @patch_user_acl
+    def test_delete_without_content(self, patch_user_acl):
         """returns 200 and deletes user without content"""
         """returns 200 and deletes user without content"""
-        override_acl(
+        patch_user_acl(
             self.user, {
             self.user, {
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_newer_than': 10,
                 'can_delete_users_with_less_posts_than': 10,
                 'can_delete_users_with_less_posts_than': 10,

+ 1 - 1
misago/users/viewmodels/posts.py

@@ -6,7 +6,7 @@ from .threads import UserThreads
 
 
 class UserPosts(UserThreads):
 class UserPosts(UserThreads):
     def get_threads_queryset(self, request, threads_categories, profile):
     def get_threads_queryset(self, request, threads_categories, profile):
-        return exclude_invisible_threads(request.user, threads_categories, Thread.objects)
+        return exclude_invisible_threads(request.user_acl, threads_categories, Thread.objects)
 
 
     def get_posts_queryset(self, user, profile, threads_queryset):
     def get_posts_queryset(self, user, profile, threads_queryset):
         return profile.post_set.select_related('thread', 'poster').filter(
         return profile.post_set.select_related('thread', 'poster').filter(

+ 3 - 3
misago/users/viewmodels/threads.py

@@ -33,8 +33,8 @@ class UserThreads(object):
 
 
         add_categories_to_items(root_category.unwrap(), threads_categories, posts + threads)
         add_categories_to_items(root_category.unwrap(), threads_categories, posts + threads)
 
 
-        add_acl(request.user, threads)
-        add_acl(request.user, posts)
+        add_acl(request.user_acl, threads)
+        add_acl(request.user_acl, posts)
 
 
         self._user = request.user
         self._user = request.user
 
 
@@ -42,7 +42,7 @@ class UserThreads(object):
         self.paginator = paginator
         self.paginator = paginator
 
 
     def get_threads_queryset(self, request, threads_categories, profile):
     def get_threads_queryset(self, request, threads_categories, profile):
-        return exclude_invisible_threads(request.user, threads_categories, profile.thread_set)
+        return exclude_invisible_threads(request.user_acl, threads_categories, profile.thread_set)
 
 
     def get_posts_queryset(self, user, profile, threads_queryset):
     def get_posts_queryset(self, user, profile, threads_queryset):
         return profile.post_set.select_related('thread', 'poster').filter(
         return profile.post_set.select_related('thread', 'poster').filter(

+ 2 - 2
misago/users/views/lists.py

@@ -11,7 +11,7 @@ from misago.users.viewmodels import ActivePosters, RankUsers
 
 
 class ListView(View):
 class ListView(View):
     def get(self, request, *args, **kwargs):
     def get(self, request, *args, **kwargs):
-        allow_browse_users_list(request.user)
+        allow_browse_users_list(request.user_acl)
 
 
         context_data = self.get_context_data(request, *args, **kwargs)
         context_data = self.get_context_data(request, *args, **kwargs)
 
 
@@ -62,7 +62,7 @@ class ListView(View):
 
 
 
 
 def landing(request):
 def landing(request):
-    allow_browse_users_list(request.user)
+    allow_browse_users_list(request.user_acl)
     return redirect(users_list.get_default_link())
     return redirect(users_list.get_default_link())
 
 
 
 

+ 3 - 3
misago/users/views/profile.py

@@ -44,7 +44,7 @@ class ProfileView(View):
             raise Http404()
             raise Http404()
 
 
         validate_slug(profile, slug)
         validate_slug(profile, slug)
-        add_acl(request.user, profile)
+        add_acl(request.user_acl, profile)
 
 
         return profile
         return profile
 
 
@@ -67,7 +67,7 @@ class ProfileView(View):
             })
             })
 
 
         request.frontend_context['PROFILE'] = UserProfileSerializer(
         request.frontend_context['PROFILE'] = UserProfileSerializer(
-            profile, context={'user': request.user}
+            profile, context={'request': request}
         ).data
         ).data
 
 
         if not profile.is_active:
         if not profile.is_active:
@@ -92,7 +92,7 @@ class ProfileView(View):
             })
             })
 
 
             if not context['show_email']:
             if not context['show_email']:
-                context['show_email'] = request.user.acl_cache['can_see_users_emails']
+                context['show_email'] = request.user_acl['can_see_users_emails']
         else:
         else:
             context.update({
             context.update({
                 'is_authenticated_user': False,
                 'is_authenticated_user': False,