Rafał Pitoń 8 years ago
parent
commit
4192bdfd1b
102 changed files with 3056 additions and 1650 deletions
  1. 3 3
      cleansource
  2. 2 2
      misago/threads/admin.py
  3. 2 2
      misago/threads/api/attachments.py
  4. 2 2
      misago/threads/api/pollvotecreateendpoint.py
  5. 1 1
      misago/threads/api/postendpoints/edits.py
  6. 2 3
      misago/threads/api/postendpoints/merge.py
  7. 5 2
      misago/threads/api/postendpoints/patch_post.py
  8. 3 1
      misago/threads/api/postendpoints/read.py
  9. 1 1
      misago/threads/api/postendpoints/split.py
  10. 4 3
      misago/threads/api/postingendpoint/attachments.py
  11. 1 1
      misago/threads/api/postingendpoint/category.py
  12. 10 4
      misago/threads/api/postingendpoint/emailnotification.py
  13. 5 3
      misago/threads/api/postingendpoint/participants.py
  14. 1 1
      misago/threads/api/postingendpoint/recordedit.py
  15. 6 2
      misago/threads/api/postingendpoint/reply.py
  16. 2 2
      misago/threads/api/postingendpoint/subscribe.py
  17. 2 1
      misago/threads/api/postingendpoint/syncprivatethreads.py
  18. 2 2
      misago/threads/api/threadendpoints/editor.py
  19. 11 9
      misago/threads/api/threadendpoints/merge.py
  20. 4 1
      misago/threads/api/threadendpoints/patch.py
  21. 3 1
      misago/threads/api/threadpoll.py
  22. 41 9
      misago/threads/api/threadposts.py
  23. 19 6
      misago/threads/api/threads.py
  24. 1 1
      misago/threads/forms.py
  25. 4 1
      misago/threads/management/commands/clearattachments.py
  26. 4 1
      misago/threads/models/attachment.py
  27. 5 2
      misago/threads/models/attachmenttype.py
  28. 1 1
      misago/threads/models/poll.py
  29. 1 1
      misago/threads/models/post.py
  30. 21 8
      misago/threads/models/thread.py
  31. 4 2
      misago/threads/moderation/threads.py
  32. 41 21
      misago/threads/participants.py
  33. 1 1
      misago/threads/permissions/attachments.py
  34. 9 7
      misago/threads/permissions/polls.py
  35. 4 4
      misago/threads/permissions/privatethreads.py
  36. 50 36
      misago/threads/permissions/threads.py
  37. 5 3
      misago/threads/search.py
  38. 4 1
      misago/threads/serializers/feed.py
  39. 13 5
      misago/threads/serializers/poll.py
  40. 8 4
      misago/threads/serializers/post.py
  41. 2 2
      misago/threads/serializers/thread.py
  42. 25 10
      misago/threads/signals.py
  43. 3 1
      misago/threads/subscriptions.py
  44. 2 1
      misago/threads/templatetags/misago_poststags.py
  45. 42 15
      misago/threads/tests/test_attachmentadmin_views.py
  46. 96 24
      misago/threads/tests/test_attachments_api.py
  47. 16 10
      misago/threads/tests/test_attachments_middleware.py
  48. 9 3
      misago/threads/tests/test_attachmenttypeadmin_views.py
  49. 31 11
      misago/threads/tests/test_attachmentview.py
  50. 51 18
      misago/threads/tests/test_emailnotification_middleware.py
  51. 1 1
      misago/threads/tests/test_events.py
  52. 14 4
      misago/threads/tests/test_floodprotection.py
  53. 26 8
      misago/threads/tests/test_participants.py
  54. 50 15
      misago/threads/tests/test_post_mentions.py
  55. 5 5
      misago/threads/tests/test_post_model.py
  56. 228 152
      misago/threads/tests/test_privatethread_patch_api.py
  57. 5 1
      misago/threads/tests/test_privatethread_reply_api.py
  58. 44 21
      misago/threads/tests/test_privatethread_start_api.py
  59. 11 3
      misago/threads/tests/test_privatethreads.py
  60. 18 14
      misago/threads/tests/test_privatethreads_api.py
  61. 15 3
      misago/threads/tests/test_search.py
  62. 32 10
      misago/threads/tests/test_subscription_middleware.py
  63. 4 1
      misago/threads/tests/test_subscriptions.py
  64. 32 11
      misago/threads/tests/test_thread_editreply_api.py
  65. 76 24
      misago/threads/tests/test_thread_merge_api.py
  66. 15 13
      misago/threads/tests/test_thread_model.py
  67. 265 133
      misago/threads/tests/test_thread_patch_api.py
  68. 8 4
      misago/threads/tests/test_thread_poll_api.py
  69. 74 32
      misago/threads/tests/test_thread_pollcreate_api.py
  70. 16 8
      misago/threads/tests/test_thread_polldelete_api.py
  71. 171 107
      misago/threads/tests/test_thread_polledit_api.py
  72. 20 10
      misago/threads/tests/test_thread_pollvotes_api.py
  73. 37 11
      misago/threads/tests/test_thread_postdelete_api.py
  74. 8 6
      misago/threads/tests/test_thread_postedits_api.py
  75. 4 2
      misago/threads/tests/test_thread_postlikes_api.py
  76. 70 34
      misago/threads/tests/test_thread_postmerge_api.py
  77. 54 30
      misago/threads/tests/test_thread_postmove_api.py
  78. 361 228
      misago/threads/tests/test_thread_postpatch_api.py
  79. 10 4
      misago/threads/tests/test_thread_postread_api.py
  80. 94 67
      misago/threads/tests/test_thread_postsplit_api.py
  81. 18 7
      misago/threads/tests/test_thread_reply_api.py
  82. 31 20
      misago/threads/tests/test_thread_start_api.py
  83. 2 2
      misago/threads/tests/test_threadparticipant_model.py
  84. 10 5
      misago/threads/tests/test_threads_api.py
  85. 53 74
      misago/threads/tests/test_threads_editor_api.py
  86. 139 109
      misago/threads/tests/test_threads_merge_api.py
  87. 3 1
      misago/threads/tests/test_threads_moderation.py
  88. 91 45
      misago/threads/tests/test_threadslists.py
  89. 18 18
      misago/threads/tests/test_threadview.py
  90. 33 21
      misago/threads/tests/test_utils.py
  91. 41 27
      misago/threads/testutils.py
  92. 76 27
      misago/threads/threadtypes/privatethread.py
  93. 111 37
      misago/threads/threadtypes/thread.py
  94. 2 3
      misago/threads/utils.py
  95. 20 12
      misago/threads/validators.py
  96. 13 3
      misago/threads/viewmodels/category.py
  97. 8 2
      misago/threads/viewmodels/posts.py
  98. 5 3
      misago/threads/viewmodels/thread.py
  99. 3 3
      misago/threads/viewmodels/threads.py
  100. 9 7
      misago/threads/views/admin/attachments.py
  101. 11 4
      misago/threads/views/goto.py
  102. 6 2
      misago/threads/views/thread.py

+ 3 - 3
cleansource

@@ -1,5 +1,5 @@
 #!/bin/bash
 #!/bin/bash
 
 
-yapf -ir misago -e '*/project_template/**/*.py' -e '*/conf/defaults.py'
-isort -rc misago
-pylint misago
+yapf -ir ${1:-misago} -e '*/project_template/**/*.py' -e '*/conf/defaults.py'
+isort -rc ${1:-misago}
+pylint ${1:-misago}

+ 2 - 2
misago/threads/admin.py

@@ -33,12 +33,12 @@ class MisagoAdminExtension(object):
             icon='fa fa-cubes',
             icon='fa fa-cubes',
             parent='misago:admin:system',
             parent='misago:admin:system',
             after='misago:admin:system:settings:index',
             after='misago:admin:system:settings:index',
-            link='misago:admin:system:attachments:index'
+            link='misago:admin:system:attachments:index',
         )
         )
         site.add_node(
         site.add_node(
             name=_("Attachment types"),
             name=_("Attachment types"),
             icon='fa fa-cube',
             icon='fa fa-cube',
             parent='misago:admin:system',
             parent='misago:admin:system',
             after='misago:admin:system:attachments:index',
             after='misago:admin:system:attachments:index',
-            link='misago:admin:system:attachment-types:index'
+            link='misago:admin:system:attachment-types:index',
         )
         )

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

@@ -87,7 +87,7 @@ def validate_filesize(upload, filetype, hard_limit):
         raise ValidationError(
         raise ValidationError(
             message % {
             message % {
                 'upload': filesizeformat(upload.size).rstrip('.0'),
                 'upload': filesizeformat(upload.size).rstrip('.0'),
-                'limit': filesizeformat(hard_limit * 1024).rstrip('.0')
+                'limit': filesizeformat(hard_limit * 1024).rstrip('.0'),
             }
             }
         )
         )
 
 
@@ -98,7 +98,7 @@ def validate_filesize(upload, filetype, hard_limit):
         raise ValidationError(
         raise ValidationError(
             message % {
             message % {
                 'upload': filesizeformat(upload.size).rstrip('.0'),
                 'upload': filesizeformat(upload.size).rstrip('.0'),
-                'limit': filesizeformat(filetype.size_limit * 1024).rstrip('.0')
+                'limit': filesizeformat(filetype.size_limit * 1024).rstrip('.0'),
             }
             }
         )
         )
 
 

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

@@ -46,7 +46,7 @@ def validate_votes(poll, votes):
             message = ungettext(
             message = ungettext(
                 "This poll disallows voting for more than %(choices)s choice.",
                 "This poll disallows voting for more than %(choices)s choice.",
                 "This poll disallows voting for more than %(choices)s choices.",
                 "This poll disallows voting for more than %(choices)s choices.",
-                poll.allowed_choices
+                poll.allowed_choices,
             )
             )
             raise ValidationError(message % {'choices': poll.allowed_choices})
             raise ValidationError(message % {'choices': poll.allowed_choices})
     except TypeError:
     except TypeError:
@@ -95,5 +95,5 @@ def set_new_votes(request, poll, final_votes):
                 voter_name=request.user.username,
                 voter_name=request.user.username,
                 voter_slug=request.user.slug,
                 voter_slug=request.user.slug,
                 choice_hash=choice['hash'],
                 choice_hash=choice['hash'],
-                voter_ip=request.user_ip
+                voter_ip=request.user_ip,
             )
             )

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

@@ -49,7 +49,7 @@ def revert_post_endpoint(request, post):
         editor_slug=request.user.slug,
         editor_slug=request.user.slug,
         editor_ip=request.user_ip,
         editor_ip=request.user_ip,
         edited_from=post.original,
         edited_from=post.original,
-        edited_to=edit.edited_from
+        edited_to=edit.edited_from,
     )
     )
 
 
     parsing_result = common_flavour(request, post.poster, edit.edited_from)
     parsing_result = common_flavour(request, post.poster, edit.edited_from)

+ 2 - 3
misago/threads/api/postendpoints/merge.py

@@ -96,9 +96,8 @@ def clean_posts_for_merge(request, thread):
                     raise MergeError(authorship_error)
                     raise MergeError(authorship_error)
 
 
             if posts[0].pk != thread.first_post_id:
             if posts[0].pk != thread.first_post_id:
-                if posts[0].is_hidden != post.is_hidden or posts[
-                        0
-                ].is_unapproved != post.is_unapproved:
+                if (posts[0].is_hidden != post.is_hidden or
+                        posts[0].is_unapproved != post.is_unapproved):
                     raise MergeError(_("Posts with different visibility can't be merged."))
                     raise MergeError(_("Posts with different visibility can't be merged."))
 
 
             posts.append(post)
             posts.append(post)

+ 5 - 2
misago/threads/api/postendpoints/patch_post.py

@@ -50,7 +50,7 @@ def patch_is_liked(request, post, value):
             liker=request.user,
             liker=request.user,
             liker_name=request.user.username,
             liker_name=request.user.username,
             liker_slug=request.user.slug,
             liker_slug=request.user.slug,
-            liker_ip=request.user_ip
+            liker_ip=request.user_ip,
         )
         )
         post.likes += 1
         post.likes += 1
 
 
@@ -61,7 +61,10 @@ def patch_is_liked(request, post, value):
 
 
     post.last_likes = []
     post.last_likes = []
     for like in post.postlike_set.all()[:4]:
     for like in post.postlike_set.all()[:4]:
-        post.last_likes.append({'id': like.liker_id, 'username': like.liker_name})
+        post.last_likes.append({
+            'id': like.liker_id,
+            'username': like.liker_name,
+        })
 
 
     post.save(update_fields=['likes', 'last_likes'])
     post.save(update_fields=['likes', 'last_likes'])
 
 

+ 3 - 1
misago/threads/api/postendpoints/read.py

@@ -10,5 +10,7 @@ def post_read_endpoint(request, thread, post):
         if thread.subscription and thread.subscription.last_read_on < post.posted_on:
         if thread.subscription and thread.subscription.last_read_on < post.posted_on:
             thread.subscription.last_read_on = post.posted_on
             thread.subscription.last_read_on = post.posted_on
             thread.subscription.save()
             thread.subscription.save()
-        return Response({'thread_is_read': thread.last_post_on <= post.posted_on})
+        return Response({
+            'thread_is_read': thread.last_post_on <= post.posted_on,
+        })
     return Response({'thread_is_read': True})
     return Response({'thread_is_read': True})

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

@@ -76,7 +76,7 @@ def split_posts_to_new_thread(request, thread, validated_data, posts):
     new_thread = Thread(
     new_thread = Thread(
         category=validated_data['category'],
         category=validated_data['category'],
         started_on=thread.started_on,
         started_on=thread.started_on,
-        last_post_on=thread.last_post_on
+        last_post_on=thread.last_post_on,
     )
     )
 
 
     new_thread.set_title(validated_data['title'])
     new_thread.set_title(validated_data['title'])

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

@@ -82,7 +82,8 @@ class AttachmentsSerializer(serializers.Serializer):
             return []
             return []
 
 
         queryset = user.attachment_set.select_related('filetype').filter(
         queryset = user.attachment_set.select_related('filetype').filter(
-            post__isnull=True, id__in=ids
+            post__isnull=True,
+            id__in=ids,
         )
         )
 
 
         return list(queryset)
         return list(queryset)
@@ -126,11 +127,11 @@ def validate_attachments_count(data):
         message = ungettext(
         message = ungettext(
             "You can't attach more than %(limit_value)s file to single post (added %(show_value)s).",
             "You can't attach more than %(limit_value)s file to single post (added %(show_value)s).",
             "You can't attach more than %(limit_value)s flies to single post (added %(show_value)s).",
             "You can't attach more than %(limit_value)s flies to single post (added %(show_value)s).",
-            settings.MISAGO_POST_ATTACHMENTS_LIMIT
+            settings.MISAGO_POST_ATTACHMENTS_LIMIT,
         )
         )
         raise serializers.ValidationError(
         raise serializers.ValidationError(
             message % {
             message % {
                 'limit_value': settings.MISAGO_POST_ATTACHMENTS_LIMIT,
                 'limit_value': settings.MISAGO_POST_ATTACHMENTS_LIMIT,
-                'show_value': total_attachments
+                'show_value': total_attachments,
             }
             }
         )
         )

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

@@ -45,7 +45,7 @@ class CategorySerializer(serializers.Serializer):
     category = serializers.IntegerField(
     category = serializers.IntegerField(
         error_messages={
         error_messages={
             'required': ugettext_lazy("You have to select category to post thread in."),
             'required': ugettext_lazy("You have to select category to post thread in."),
-            'invalid': ugettext_lazy("Selected category is invalid.")
+            'invalid': ugettext_lazy("Selected category is invalid."),
         }
         }
     )
     )
 
 

+ 10 - 4
misago/threads/api/postingendpoint/emailnotification.py

@@ -17,7 +17,8 @@ class EmailNotificationMiddleware(PostingMiddleware):
 
 
     def post_save(self, serializer):
     def post_save(self, serializer):
         queryset = self.thread.subscription_set.filter(
         queryset = self.thread.subscription_set.filter(
-            send_email=True, last_read_on__gte=self.previous_last_post_on
+            send_email=True,
+            last_read_on__gte=self.previous_last_post_on,
         ).exclude(user=self.user).select_related('user')
         ).exclude(user=self.user).select_related('user')
 
 
         notifications = []
         notifications = []
@@ -42,7 +43,12 @@ class EmailNotificationMiddleware(PostingMiddleware):
         subject_formats = {'user': self.user.username, 'thread': self.thread.title}
         subject_formats = {'user': self.user.username, 'thread': self.thread.title}
 
 
         return build_mail(
         return build_mail(
-            self.request, subscriber, subject % subject_formats, 'misago/emails/thread/reply',
-            {'thread': self.thread,
-             'post': self.post}
+            self.request,
+            subscriber,
+            subject % subject_formats,
+            'misago/emails/thread/reply',
+            {
+                'thread': self.thread,
+                'post': self.post,
+            },
         )
         )

+ 5 - 3
misago/threads/api/postingendpoint/participants.py

@@ -58,11 +58,13 @@ class ParticipantsSerializer(serializers.Serializer):
             message = ungettext(
             message = ungettext(
                 "You can't add more than %(users)s user to private thread (you've added %(added)s).",
                 "You can't add more than %(users)s user to private thread (you've added %(added)s).",
                 "You can't add more than %(users)s users to private thread (you've added %(added)s).",
                 "You can't add more than %(users)s users to private thread (you've added %(added)s).",
-                max_participants
+                max_participants,
             )
             )
             raise serializers.ValidationError(
             raise serializers.ValidationError(
-                message % {'users': max_participants,
-                           'added': len(clean_usernames)}
+                message % {
+                    'users': max_participants,
+                    'added': len(clean_usernames),
+                }
             )
             )
 
 
         return list(set(clean_usernames))
         return list(set(clean_usernames))

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

@@ -34,5 +34,5 @@ class RecordEditMiddleware(PostingMiddleware):
             editor_slug=self.user.slug,
             editor_slug=self.user.slug,
             editor_ip=self.request.user_ip,
             editor_ip=self.request.user_ip,
             edited_from=self.original_post,
             edited_from=self.original_post,
-            edited_to=self.post.original
+            edited_to=self.post.original,
         )
         )

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

@@ -84,12 +84,16 @@ class ReplyMiddleware(PostingMiddleware):
 class ReplySerializer(serializers.Serializer):
 class ReplySerializer(serializers.Serializer):
     post = serializers.CharField(
     post = serializers.CharField(
         validators=[validate_post],
         validators=[validate_post],
-        error_messages={'required': ugettext_lazy("You have to enter a message.")}
+        error_messages={
+            'required': ugettext_lazy("You have to enter a message."),
+        }
     )
     )
 
 
 
 
 class ThreadSerializer(ReplySerializer):
 class ThreadSerializer(ReplySerializer):
     title = serializers.CharField(
     title = serializers.CharField(
         validators=[validate_title],
         validators=[validate_title],
-        error_messages={'required': ugettext_lazy("You have to enter thread title.")}
+        error_messages={
+            'required': ugettext_lazy("You have to enter thread title."),
+        }
     )
     )

+ 2 - 2
misago/threads/api/postingendpoint/subscribe.py

@@ -26,7 +26,7 @@ class SubscribeMiddleware(PostingMiddleware):
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             category=self.thread.category,
             category=self.thread.category,
             thread=self.thread,
             thread=self.thread,
-            send_email=self.user.subscribe_to_started_threads == UserModel.SUBSCRIBE_ALL
+            send_email=self.user.subscribe_to_started_threads == UserModel.SUBSCRIBE_ALL,
         )
         )
 
 
     def subscribe_replied_thread(self):
     def subscribe_replied_thread(self):
@@ -48,5 +48,5 @@ class SubscribeMiddleware(PostingMiddleware):
         self.user.subscription_set.create(
         self.user.subscription_set.create(
             category=self.thread.category,
             category=self.thread.category,
             thread=self.thread,
             thread=self.thread,
-            send_email=self.user.subscribe_to_replied_threads == UserModel.SUBSCRIBE_ALL
+            send_email=self.user.subscribe_to_replied_threads == UserModel.SUBSCRIBE_ALL,
         )
         )

+ 2 - 1
misago/threads/api/postingendpoint/syncprivatethreads.py

@@ -16,5 +16,6 @@ class SyncPrivateThreadsMiddleware(PostingMiddleware):
 
 
     def post_save(self, serializer):
     def post_save(self, serializer):
         set_users_unread_private_threads_sync(
         set_users_unread_private_threads_sync(
-            participants=self.thread.participants_list, exclude_user=self.user
+            participants=self.thread.participants_list,
+            exclude_user=self.user,
         )
         )

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

@@ -31,7 +31,7 @@ def thread_start_editor(request):
             post = {
             post = {
                 'close': bool(category.acl['can_close_threads']),
                 'close': bool(category.acl['can_close_threads']),
                 'hide': bool(category.acl['can_hide_threads']),
                 'hide': bool(category.acl['can_hide_threads']),
-                'pin': category.acl['can_pin_threads']
+                'pin': category.acl['can_pin_threads'],
             }
             }
 
 
             available.append(category.pk)
             available.append(category.pk)
@@ -43,7 +43,7 @@ def thread_start_editor(request):
             'id': category.pk,
             'id': category.pk,
             'name': category.name,
             'name': category.name,
             'level': category.level - 1,
             'level': category.level - 1,
-            'post': post
+            'post': post,
         })
         })
 
 
     # list only categories that allow new threads, or contains subcategory that allows one
     # list only categories that allow new threads, or contains subcategory that allows one

+ 11 - 9
misago/threads/api/threadendpoints/merge.py

@@ -48,14 +48,16 @@ def thread_merge_endpoint(request, thread, viewmodel):
     except PermissionDenied as e:
     except PermissionDenied as e:
         return Response({'detail': e.args[0]}, status=400)
         return Response({'detail': e.args[0]}, status=400)
     except Http404:
     except Http404:
-        return Response({
-            'detail':
-                _(
-                    "The thread you have entered link to doesn't "
-                    "exist or you don't have permission to see it."
-                )
-        },
-                        status=400)
+        return Response(
+            {
+                'detail':
+                    _(
+                        "The thread you have entered link to doesn't "
+                        "exist or you don't have permission to see it."
+                    )
+            },
+            status=400,
+        )
 
 
     polls_handler = PollMergeHandler([thread, other_thread])
     polls_handler = PollMergeHandler([thread, other_thread])
     if len(polls_handler.polls) == 1:
     if len(polls_handler.polls) == 1:
@@ -91,7 +93,7 @@ def thread_merge_endpoint(request, thread, viewmodel):
     return Response({
     return Response({
         'id': other_thread.pk,
         'id': other_thread.pk,
         'title': other_thread.title,
         'title': other_thread.title,
-        'url': other_thread.get_absolute_url()
+        'url': other_thread.get_absolute_url(),
     })
     })
 
 
 
 

+ 4 - 1
misago/threads/api/threadendpoints/patch.py

@@ -262,7 +262,10 @@ def patch_remove_participant(request, thread, value):
         make_participants_aware(request.user, thread)
         make_participants_aware(request.user, thread)
         participants = ThreadParticipantSerializer(thread.participants_list, many=True)
         participants = ThreadParticipantSerializer(thread.participants_list, many=True)
 
 
-        return {'deleted': False, 'participants': participants.data}
+        return {
+            'deleted': False,
+            'participants': participants.data,
+        }
 
 
 
 
 thread_patch_dispatcher.remove('participants', patch_remove_participant)
 thread_patch_dispatcher.remove('participants', patch_remove_participant)

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

@@ -112,7 +112,9 @@ class ViewSet(viewsets.ViewSet):
         thread.has_poll = False
         thread.has_poll = False
         thread.save()
         thread.save()
 
 
-        return Response({'can_start_poll': can_start_poll(request.user, thread)})
+        return Response({
+            'can_start_poll': can_start_poll(request.user, thread),
+        })
 
 
     @detail_route(methods=['get', 'post'])
     @detail_route(methods=['get', 'post'])
     def votes(self, request, thread_pk, pk):
     def votes(self, request, thread_pk, pk):

+ 41 - 9
misago/threads/api/threadposts.py

@@ -33,15 +33,29 @@ class ViewSet(viewsets.ViewSet):
     post_ = ThreadPost
     post_ = ThreadPost
 
 
     def get_thread(
     def get_thread(
-            self, request, pk, read_aware=True, subscription_aware=True, select_for_update=False
+            self,
+            request,
+            pk,
+            read_aware=True,
+            subscription_aware=True,
+            select_for_update=False,
     ):
     ):
         return self.thread(
         return self.thread(
-            request, get_int_or_404(pk), None, read_aware, subscription_aware, select_for_update
+            request,
+            get_int_or_404(pk),
+            None,
+            read_aware,
+            subscription_aware,
+            select_for_update,
         )
         )
 
 
     def get_thread_for_update(self, request, pk):
     def get_thread_for_update(self, request, pk):
         return self.get_thread(
         return self.get_thread(
-            request, pk, read_aware=False, subscription_aware=False, select_for_update=True
+            request,
+            pk,
+            read_aware=False,
+            subscription_aware=False,
+            select_for_update=True,
         )
         )
 
 
     def get_posts(self, request, thread, page):
     def get_posts(self, request, thread, page):
@@ -89,10 +103,18 @@ class ViewSet(viewsets.ViewSet):
         thread = self.get_thread_for_update(request, thread_pk).unwrap()
         thread = self.get_thread_for_update(request, thread_pk).unwrap()
         allow_reply_thread(request.user, thread)
         allow_reply_thread(request.user, thread)
 
 
-        post = Post(thread=thread, category=thread.category)
+        post = Post(
+            thread=thread,
+            category=thread.category,
+        )
 
 
         # Put them through posting pipeline
         # Put them through posting pipeline
-        posting = PostingEndpoint(request, PostingEndpoint.REPLY, thread=thread, post=post)
+        posting = PostingEndpoint(
+            request,
+            PostingEndpoint.REPLY,
+            thread=thread,
+            post=post,
+        )
 
 
         if posting.is_valid():
         if posting.is_valid():
             user_posts = request.user.posts
             user_posts = request.user.posts
@@ -117,7 +139,12 @@ class ViewSet(viewsets.ViewSet):
 
 
         allow_edit_post(request.user, post)
         allow_edit_post(request.user, post)
 
 
-        posting = PostingEndpoint(request, PostingEndpoint.EDIT, thread=thread, post=post)
+        posting = PostingEndpoint(
+            request,
+            PostingEndpoint.EDIT,
+            thread=thread,
+            post=post,
+        )
 
 
         if posting.is_valid():
         if posting.is_valid():
             post_edits = post.edits
             post_edits = post.edits
@@ -177,7 +204,12 @@ class ViewSet(viewsets.ViewSet):
 
 
     @detail_route(methods=['get'], url_path='editor')
     @detail_route(methods=['get'], url_path='editor')
     def post_editor(self, request, thread_pk, pk):
     def post_editor(self, request, thread_pk, pk):
-        thread = self.get_thread(request, thread_pk, read_aware=False, subscription_aware=False)
+        thread = self.get_thread(
+            request,
+            thread_pk,
+            read_aware=False,
+            subscription_aware=False,
+        )
         post = self.get_post(request, thread, pk).unwrap()
         post = self.get_post(request, thread, pk).unwrap()
 
 
         allow_edit_post(request.user, post)
         allow_edit_post(request.user, post)
@@ -197,7 +229,7 @@ class ViewSet(viewsets.ViewSet):
             'attachments': attachments_json,
             'attachments': attachments_json,
             'can_protect': bool(thread.category.acl['can_protect_posts']),
             'can_protect': bool(thread.category.acl['can_protect_posts']),
             'is_protected': post.is_protected,
             'is_protected': post.is_protected,
-            'poster': post.poster_name
+            'poster': post.poster_name,
         })
         })
 
 
     @list_route(methods=['get'], url_path='editor')
     @list_route(methods=['get'], url_path='editor')
@@ -218,7 +250,7 @@ class ViewSet(viewsets.ViewSet):
             return Response({
             return Response({
                 'id': reply_to.pk,
                 'id': reply_to.pk,
                 'post': reply_to.original,
                 'post': reply_to.original,
-                'poster': reply_to.poster_name
+                'poster': reply_to.poster_name,
             })
             })
         else:
         else:
             return Response({})
             return Response({})

+ 19 - 6
misago/threads/api/threads.py

@@ -28,12 +28,21 @@ class ViewSet(viewsets.ViewSet):
             self, request, pk, read_aware=True, subscription_aware=True, select_for_update=False
             self, request, pk, read_aware=True, subscription_aware=True, select_for_update=False
     ):
     ):
         return self.thread(
         return self.thread(
-            request, get_int_or_404(pk), None, read_aware, subscription_aware, select_for_update
+            request,
+            get_int_or_404(pk),
+            None,
+            read_aware,
+            subscription_aware,
+            select_for_update,
         )
         )
 
 
     def get_thread_for_update(self, request, pk):
     def get_thread_for_update(self, request, pk):
         return self.get_thread(
         return self.get_thread(
-            request, pk, read_aware=False, subscription_aware=False, select_for_update=True
+            request,
+            pk,
+            read_aware=False,
+            subscription_aware=False,
+            select_for_update=True,
         )
         )
 
 
     def retrieve(self, request, pk):
     def retrieve(self, request, pk):
@@ -70,7 +79,11 @@ class ThreadViewSet(ViewSet):
 
 
         # Put them through posting pipeline
         # Put them through posting pipeline
         posting = PostingEndpoint(
         posting = PostingEndpoint(
-            request, PostingEndpoint.START, tree_name=THREADS_ROOT_NAME, thread=thread, post=post
+            request,
+            PostingEndpoint.START,
+            tree_name=THREADS_ROOT_NAME,
+            thread=thread,
+            post=post,
         )
         )
 
 
         if posting.is_valid():
         if posting.is_valid():
@@ -79,7 +92,7 @@ class ThreadViewSet(ViewSet):
             return Response({
             return Response({
                 'id': thread.pk,
                 'id': thread.pk,
                 'title': thread.title,
                 'title': thread.title,
-                'url': thread.get_absolute_url()
+                'url': thread.get_absolute_url(),
             })
             })
         else:
         else:
             return Response(posting.errors, status=400)
             return Response(posting.errors, status=400)
@@ -128,7 +141,7 @@ class PrivateThreadViewSet(ViewSet):
             PostingEndpoint.START,
             PostingEndpoint.START,
             tree_name=PRIVATE_THREADS_ROOT_NAME,
             tree_name=PRIVATE_THREADS_ROOT_NAME,
             thread=thread,
             thread=thread,
-            post=post
+            post=post,
         )
         )
 
 
         if posting.is_valid():
         if posting.is_valid():
@@ -137,7 +150,7 @@ class PrivateThreadViewSet(ViewSet):
             return Response({
             return Response({
                 'id': thread.pk,
                 'id': thread.pk,
                 'title': thread.title,
                 'title': thread.title,
-                'url': thread.get_absolute_url()
+                'url': thread.get_absolute_url(),
             })
             })
         else:
         else:
             return Response(posting.errors, status=400)
             return Response(posting.errors, status=400)

+ 1 - 1
misago/threads/forms.py

@@ -18,7 +18,7 @@ class SearchAttachmentsForm(forms.Form):
         coerce=int,
         coerce=int,
         choices=get_searchable_filetypes,
         choices=get_searchable_filetypes,
         empty_value=0,
         empty_value=0,
-        required=False
+        required=False,
     )
     )
     is_orphan = forms.ChoiceField(
     is_orphan = forms.ChoiceField(
         label=_("State"),
         label=_("State"),

+ 4 - 1
misago/threads/management/commands/clearattachments.py

@@ -15,7 +15,10 @@ class Command(BaseCommand):
 
 
     def handle(self, *args, **options):
     def handle(self, *args, **options):
         cutoff = timezone.now() - timedelta(minutes=settings.MISAGO_ATTACHMENT_ORPHANED_EXPIRE)
         cutoff = timezone.now() - timedelta(minutes=settings.MISAGO_ATTACHMENT_ORPHANED_EXPIRE)
-        queryset = Attachment.objects.filter(post__isnull=True, uploaded_on__lt=cutoff)
+        queryset = Attachment.objects.filter(
+            post__isnull=True,
+            uploaded_on__lt=cutoff,
+        )
 
 
         attachments_to_sync = queryset.count()
         attachments_to_sync = queryset.count()
 
 

+ 4 - 1
misago/threads/models/attachment.py

@@ -39,7 +39,10 @@ class Attachment(models.Model):
     uploaded_on = models.DateTimeField(default=timezone.now, db_index=True)
     uploaded_on = models.DateTimeField(default=timezone.now, db_index=True)
 
 
     uploader = models.ForeignKey(
     uploader = models.ForeignKey(
-        settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL
+        settings.AUTH_USER_MODEL,
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
     )
     )
     uploader_name = models.CharField(max_length=255)
     uploader_name = models.CharField(max_length=255)
     uploader_slug = models.CharField(max_length=255, db_index=True)
     uploader_slug = models.CharField(max_length=255, db_index=True)

+ 5 - 2
misago/threads/models/attachmenttype.py

@@ -17,8 +17,11 @@ class AttachmentType(models.Model):
     size_limit = models.PositiveIntegerField(default=1024)
     size_limit = models.PositiveIntegerField(default=1024)
     status = models.PositiveIntegerField(
     status = models.PositiveIntegerField(
         default=ENABLED,
         default=ENABLED,
-        choices=((ENABLED, _("Allow uploads and downloads")), (LOCKED, _("Allow downloads only")),
-                 (DISABLED, _("Disallow both uploading and downloading")), )
+        choices=[
+            (ENABLED, _("Allow uploads and downloads")),
+            (LOCKED, _("Allow downloads only")),
+            (DISABLED, _("Disallow both uploading and downloading")),
+        ],
     )
     )
 
 
     limit_uploads_to = models.ManyToManyField('misago_acl.Role', related_name='+', blank=True)
     limit_uploads_to = models.ManyToManyField('misago_acl.Role', related_name='+', blank=True)

+ 1 - 1
misago/threads/models/poll.py

@@ -93,6 +93,6 @@ class Poll(models.Model):
                 'label': choice['label'],
                 'label': choice['label'],
                 'votes': choice['votes'],
                 'votes': choice['votes'],
                 'selected': choice['selected'],
                 'selected': choice['selected'],
-                'proc': proc
+                'proc': proc,
             })
             })
         return view_choices
         return view_choices

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

@@ -84,7 +84,7 @@ class Post(models.Model):
         index_together = [
         index_together = [
             ('thread', 'id'),  # speed up threadview for team members
             ('thread', 'id'),  # speed up threadview for team members
             ('is_event', 'is_hidden'),
             ('is_event', 'is_hidden'),
-            ('poster', 'posted_on')
+            ('poster', 'posted_on'),
         ]
         ]
 
 
     def __str__(self):
     def __str__(self):

+ 21 - 8
misago/threads/models/thread.py

@@ -13,9 +13,11 @@ class Thread(models.Model):
     WEIGHT_PINNED = 1
     WEIGHT_PINNED = 1
     WEIGHT_GLOBAL = 2
     WEIGHT_GLOBAL = 2
 
 
-    WEIGHT_CHOICES = ((WEIGHT_DEFAULT, _("Don't pin thread")),
-                      (WEIGHT_PINNED, _("Pin thread within category")),
-                      (WEIGHT_GLOBAL, _("Pin thread globally")))
+    WEIGHT_CHOICES = [
+        (WEIGHT_DEFAULT, _("Don't pin thread")),
+        (WEIGHT_PINNED, _("Pin thread within category")),
+        (WEIGHT_GLOBAL, _("Pin thread globally")),
+    ]
 
 
     category = models.ForeignKey('misago_categories.Category')
     category = models.ForeignKey('misago_categories.Category')
     title = models.CharField(max_length=255)
     title = models.CharField(max_length=255)
@@ -33,16 +35,27 @@ class Thread(models.Model):
     last_post_on = models.DateTimeField(db_index=True)
     last_post_on = models.DateTimeField(db_index=True)
 
 
     first_post = models.ForeignKey(
     first_post = models.ForeignKey(
-        'misago_threads.Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL
+        'misago_threads.Post',
+        related_name='+',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
     )
     )
     starter = models.ForeignKey(
     starter = models.ForeignKey(
-        settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
+        settings.AUTH_USER_MODEL,
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
     )
     )
     starter_name = models.CharField(max_length=255)
     starter_name = models.CharField(max_length=255)
     starter_slug = models.CharField(max_length=255)
     starter_slug = models.CharField(max_length=255)
 
 
     last_post = models.ForeignKey(
     last_post = models.ForeignKey(
-        'misago_threads.Post', related_name='+', null=True, blank=True, on_delete=models.SET_NULL
+        'misago_threads.Post',
+        related_name='+',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
     )
     )
     last_post_is_event = models.BooleanField(default=False)
     last_post_is_event = models.BooleanField(default=False)
     last_poster = models.ForeignKey(
     last_poster = models.ForeignKey(
@@ -50,7 +63,7 @@ class Thread(models.Model):
         related_name='last_poster_set',
         related_name='last_poster_set',
         null=True,
         null=True,
         blank=True,
         blank=True,
-        on_delete=models.SET_NULL
+        on_delete=models.SET_NULL,
     )
     )
     last_poster_name = models.CharField(max_length=255, null=True, blank=True)
     last_poster_name = models.CharField(max_length=255, null=True, blank=True)
     last_poster_slug = models.CharField(max_length=255, null=True, blank=True)
     last_poster_slug = models.CharField(max_length=255, null=True, blank=True)
@@ -65,7 +78,7 @@ class Thread(models.Model):
         settings.AUTH_USER_MODEL,
         settings.AUTH_USER_MODEL,
         related_name='privatethread_set',
         related_name='privatethread_set',
         through='ThreadParticipant',
         through='ThreadParticipant',
-        through_fields=('thread', 'user')
+        through_fields=('thread', 'user'),
     )
     )
 
 
     class Meta:
     class Meta:

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

@@ -33,7 +33,9 @@ def change_thread_title(request, thread, new_title):
         thread.first_post.update_search_vector()
         thread.first_post.update_search_vector()
         thread.first_post.save(update_fields=['search_vector'])
         thread.first_post.save(update_fields=['search_vector'])
 
 
-        record_event(request, thread, 'changed_title', {'old_title': old_title})
+        record_event(request, thread, 'changed_title', {
+            'old_title': old_title,
+        })
         return True
         return True
     else:
     else:
         return False
         return False
@@ -80,7 +82,7 @@ def move_thread(request, thread, new_category):
                 'from_category': {
                 'from_category': {
                     'name': from_category.name,
                     'name': from_category.name,
                     'url': from_category.get_absolute_url(),
                     'url': from_category.get_absolute_url(),
-                }
+                },
             }
             }
         )
         )
         return True
         return True

+ 41 - 21
misago/threads/participants.py

@@ -28,7 +28,8 @@ def make_threads_participants_aware(user, threads):
         threads_dict[thread.pk] = thread
         threads_dict[thread.pk] = thread
 
 
     participants_qs = ThreadParticipant.objects.filter(
     participants_qs = ThreadParticipant.objects.filter(
-        user=user, thread_id__in=threads_dict.keys()
+        user=user,
+        thread_id__in=threads_dict.keys(),
     )
     )
 
 
     for participant in participants_qs:
     for participant in participants_qs:
@@ -78,16 +79,21 @@ def change_owner(request, thread, user):
     """
     """
     ThreadParticipant.objects.set_owner(thread, user)
     ThreadParticipant.objects.set_owner(thread, user)
     set_users_unread_private_threads_sync(
     set_users_unread_private_threads_sync(
-        participants=thread.participants_list, exclude_user=request.user
+        participants=thread.participants_list,
+        exclude_user=request.user,
     )
     )
 
 
     if thread.participant and thread.participant.is_owner:
     if thread.participant and thread.participant.is_owner:
         record_event(
         record_event(
-            request, thread, 'changed_owner',
-            {'user': {
-                'username': user.username,
-                'url': user.get_absolute_url(),
-            }}
+            request,
+            thread,
+            'changed_owner',
+            {
+                'user': {
+                    'username': user.username,
+                    'url': user.get_absolute_url(),
+                },
+            },
         )
         )
     else:
     else:
         record_event(request, thread, 'tookover')
         record_event(request, thread, 'tookover')
@@ -103,11 +109,15 @@ def add_participant(request, thread, user):
         record_event(request, thread, 'entered_thread')
         record_event(request, thread, 'entered_thread')
     else:
     else:
         record_event(
         record_event(
-            request, thread, 'added_participant',
-            {'user': {
-                'username': user.username,
-                'url': user.get_absolute_url(),
-            }}
+            request,
+            thread,
+            'added_participant',
+            {
+                'user': {
+                    'username': user.username,
+                    'url': user.get_absolute_url(),
+                },
+            },
         )
         )
 
 
 
 
@@ -124,7 +134,9 @@ def add_participants(request, thread, users):
         thread_participants = []
         thread_participants = []
 
 
     set_users_unread_private_threads_sync(
     set_users_unread_private_threads_sync(
-        users=users, participants=thread_participants, exclude_user=request.user
+        users=users,
+        participants=thread_participants,
+        exclude_user=request.user,
     )
     )
 
 
     emails = []
     emails = []
@@ -137,11 +149,15 @@ def add_participants(request, thread, users):
 
 
 def build_noticiation_email(request, thread, user):
 def build_noticiation_email(request, thread, user):
     subject = _('%(user)s has invited you to participate in private thread "%(thread)s"')
     subject = _('%(user)s has invited you to participate in private thread "%(thread)s"')
-    subject_formats = {'thread': thread.title, 'user': request.user.username}
+    subject_formats = {
+        'thread': thread.title,
+        'user': request.user.username,
+    }
 
 
     return build_mail(
     return build_mail(
-        request, user, subject % subject_formats, 'misago/emails/privatethread/added',
-        {'thread': thread}
+        request, user, subject % subject_formats, 'misago/emails/privatethread/added', {
+            'thread': thread,
+        }
     )
     )
 
 
 
 
@@ -180,9 +196,13 @@ def remove_participant(request, thread, user):
                 event_type = 'removed_participant'
                 event_type = 'removed_participant'
 
 
         record_event(
         record_event(
-            request, thread, event_type,
-            {'user': {
-                'username': user.username,
-                'url': user.get_absolute_url(),
-            }}
+            request,
+            thread,
+            event_type,
+            {
+                'user': {
+                    'username': user.username,
+                    'url': user.get_absolute_url(),
+                },
+            },
         )
         )

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

@@ -63,7 +63,7 @@ def build_acl(acl, roles, key_name):
         key=key_name,
         key=key_name,
         max_attachment_size=algebra.greater,
         max_attachment_size=algebra.greater,
         can_download_other_users_attachments=algebra.greater,
         can_download_other_users_attachments=algebra.greater,
-        can_delete_other_users_attachments=algebra.greater
+        can_delete_other_users_attachments=algebra.greater,
     )
     )
 
 
 
 

+ 9 - 7
misago/threads/permissions/polls.py

@@ -35,29 +35,29 @@ class RolePermissionsForm(forms.Form):
         label=_("Can start polls"),
         label=_("Can start polls"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Own threads")), (2, _("All threads")))
+        choices=((0, _("No")), (1, _("Own threads")), (2, _("All threads")), ),
     )
     )
     can_edit_polls = forms.TypedChoiceField(
     can_edit_polls = forms.TypedChoiceField(
         label=_("Can edit polls"),
         label=_("Can edit polls"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Own polls")), (2, _("All polls")))
+        choices=((0, _("No")), (1, _("Own polls")), (2, _("All polls")), ),
     )
     )
     can_delete_polls = forms.TypedChoiceField(
     can_delete_polls = forms.TypedChoiceField(
         label=_("Can delete polls"),
         label=_("Can delete polls"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Own polls")), (2, _("All polls")))
+        choices=((0, _("No")), (1, _("Own polls")), (2, _("All polls")), ),
     )
     )
     poll_edit_time = forms.IntegerField(
     poll_edit_time = forms.IntegerField(
         label=_("Time limit for own polls edits, in minutes"),
         label=_("Time limit for own polls edits, in minutes"),
         help_text=_("Enter 0 to don't limit time for editing own polls."),
         help_text=_("Enter 0 to don't limit time for editing own polls."),
         initial=0,
         initial=0,
-        min_value=0
+        min_value=0,
     )
     )
     can_always_see_poll_voters = YesNoSwitch(
     can_always_see_poll_voters = YesNoSwitch(
         label=_("Can always see polls voters"),
         label=_("Can always see polls voters"),
-        help_text=_("Allows users to see who voted in poll even if poll votes are secret.")
+        help_text=_("Allows users to see who voted in poll even if poll votes are secret."),
     )
     )
 
 
 
 
@@ -79,7 +79,7 @@ def build_acl(acl, roles, key_name):
         'can_edit_polls': 0,
         'can_edit_polls': 0,
         'can_delete_polls': 0,
         'can_delete_polls': 0,
         'poll_edit_time': 0,
         'poll_edit_time': 0,
-        'can_always_see_poll_voters': 0
+        'can_always_see_poll_voters': 0,
     })
     })
 
 
     return algebra.sum_acls(
     return algebra.sum_acls(
@@ -109,7 +109,9 @@ def add_acl_to_poll(user, poll):
 
 
 
 
 def add_acl_to_thread(user, thread):
 def add_acl_to_thread(user, thread):
-    thread.acl.update({'can_start_poll': can_start_poll(user, thread)})
+    thread.acl.update({
+        'can_start_poll': can_start_poll(user, thread),
+    })
 
 
 
 
 def register_with(registry):
 def register_with(registry):

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

@@ -42,25 +42,25 @@ class PermissionsForm(forms.Form):
         label=_("Max number of users invited to private thread"),
         label=_("Max number of users invited to private thread"),
         help_text=_("Enter 0 to don't limit number of participants."),
         help_text=_("Enter 0 to don't limit number of participants."),
         initial=3,
         initial=3,
-        min_value=0
+        min_value=0,
     )
     )
     can_add_everyone_to_private_threads = YesNoSwitch(
     can_add_everyone_to_private_threads = YesNoSwitch(
         label=_("Can add everyone to threads"),
         label=_("Can add everyone to threads"),
-        help_text=_("Allows user to add users that are blocking him to private threads.")
+        help_text=_("Allows user to add users that are blocking him to private threads."),
     )
     )
     can_report_private_threads = YesNoSwitch(
     can_report_private_threads = YesNoSwitch(
         label=_("Can report private threads"),
         label=_("Can report private threads"),
         help_text=_(
         help_text=_(
             "Allows user to report private threads they are "
             "Allows user to report private threads they are "
             "participating in, making them accessible to moderators."
             "participating in, making them accessible to moderators."
-        )
+        ),
     )
     )
     can_moderate_private_threads = YesNoSwitch(
     can_moderate_private_threads = YesNoSwitch(
         label=_("Can moderate private threads"),
         label=_("Can moderate private threads"),
         help_text=_(
         help_text=_(
             "Allows user to read, reply, edit and delete content "
             "Allows user to read, reply, edit and delete content "
             "in reported private threads."
             "in reported private threads."
-        )
+        ),
     )
     )
 
 
 
 

+ 50 - 36
misago/threads/permissions/threads.py

@@ -62,7 +62,7 @@ class RolePermissionsForm(forms.Form):
             "threads lists, it will only display threads belonging to "
             "threads lists, it will only display threads belonging to "
             "categories in which the user has permission to approve "
             "categories in which the user has permission to approve "
             "content."
             "content."
-        )
+        ),
     )
     )
     can_see_reported_content_lists = YesNoSwitch(
     can_see_reported_content_lists = YesNoSwitch(
         label=_("Can see reported content list"),
         label=_("Can see reported content list"),
@@ -73,11 +73,11 @@ class RolePermissionsForm(forms.Form):
             "threads lists, it will only display threads belonging to "
             "threads lists, it will only display threads belonging to "
             "categories in which the user has permission to see posts "
             "categories in which the user has permission to see posts "
             "reports."
             "reports."
-        )
+        ),
     )
     )
     can_omit_flood_protection = YesNoSwitch(
     can_omit_flood_protection = YesNoSwitch(
         label=_("Can omit flood protection"),
         label=_("Can omit flood protection"),
-        help_text=_("Allows posting more frequently than flood protection would.")
+        help_text=_("Allows posting more frequently than flood protection would."),
     )
     )
 
 
 
 
@@ -88,7 +88,7 @@ class CategoryPermissionsForm(forms.Form):
         label=_("Can see threads"),
         label=_("Can see threads"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("Started threads")), (1, _("All threads")))
+        choices=((0, _("Started threads")), (1, _("All threads")), ),
     )
     )
 
 
     can_start_threads = YesNoSwitch(label=_("Can start threads"))
     can_start_threads = YesNoSwitch(label=_("Can start threads"))
@@ -98,7 +98,7 @@ class CategoryPermissionsForm(forms.Form):
         label=_("Can edit threads"),
         label=_("Can edit threads"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Own threads")), (2, _("All threads")))
+        choices=((0, _("No")), (1, _("Own threads")), (2, _("All threads")), ),
     )
     )
     can_hide_own_threads = forms.TypedChoiceField(
     can_hide_own_threads = forms.TypedChoiceField(
         label=_("Can hide own threads"),
         label=_("Can hide own threads"),
@@ -108,26 +108,26 @@ class CategoryPermissionsForm(forms.Form):
         ),
         ),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Hide threads")), (2, _("Delete threads")))
+        choices=((0, _("No")), (1, _("Hide threads")), (2, _("Delete threads")), ),
     )
     )
     thread_edit_time = forms.IntegerField(
     thread_edit_time = forms.IntegerField(
         label=_("Time limit for own threads edits, in minutes"),
         label=_("Time limit for own threads edits, in minutes"),
         help_text=_("Enter 0 to don't limit time for editing own threads."),
         help_text=_("Enter 0 to don't limit time for editing own threads."),
         initial=0,
         initial=0,
-        min_value=0
+        min_value=0,
     )
     )
     can_hide_threads = forms.TypedChoiceField(
     can_hide_threads = forms.TypedChoiceField(
         label=_("Can hide all threads"),
         label=_("Can hide all threads"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Hide threads")), (2, _("Delete threads")))
+        choices=((0, _("No")), (1, _("Hide threads")), (2, _("Delete threads")), ),
     )
     )
 
 
     can_pin_threads = forms.TypedChoiceField(
     can_pin_threads = forms.TypedChoiceField(
         label=_("Can pin threads"),
         label=_("Can pin threads"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Locally")), (2, _("Globally")))
+        choices=((0, _("No")), (1, _("Locally")), (2, _("Globally")), ),
     )
     )
     can_close_threads = YesNoSwitch(label=_("Can close threads"))
     can_close_threads = YesNoSwitch(label=_("Can close threads"))
     can_move_threads = YesNoSwitch(label=_("Can move threads"))
     can_move_threads = YesNoSwitch(label=_("Can move threads"))
@@ -137,42 +137,42 @@ class CategoryPermissionsForm(forms.Form):
         label=_("Can edit posts"),
         label=_("Can edit posts"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Own posts")), (2, _("All posts")))
+        choices=((0, _("No")), (1, _("Own posts")), (2, _("All posts")), ),
     )
     )
     can_hide_own_posts = forms.TypedChoiceField(
     can_hide_own_posts = forms.TypedChoiceField(
         label=_("Can hide own posts"),
         label=_("Can hide own posts"),
         help_text=_("Only last posts to thread made within edit time limit can be hidden."),
         help_text=_("Only last posts to thread made within edit time limit can be hidden."),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Hide posts")), (2, _("Delete posts")))
+        choices=((0, _("No")), (1, _("Hide posts")), (2, _("Delete posts")), ),
     )
     )
     post_edit_time = forms.IntegerField(
     post_edit_time = forms.IntegerField(
         label=_("Time limit for own post edits, in minutes"),
         label=_("Time limit for own post edits, in minutes"),
         help_text=_("Enter 0 to don't limit time for editing own posts."),
         help_text=_("Enter 0 to don't limit time for editing own posts."),
         initial=0,
         initial=0,
-        min_value=0
+        min_value=0,
     )
     )
     can_hide_posts = forms.TypedChoiceField(
     can_hide_posts = forms.TypedChoiceField(
         label=_("Can hide all posts"),
         label=_("Can hide all posts"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Hide posts")), (2, _("Delete posts")))
+        choices=((0, _("No")), (1, _("Hide posts")), (2, _("Delete posts")), ),
     )
     )
 
 
     can_see_posts_likes = forms.TypedChoiceField(
     can_see_posts_likes = forms.TypedChoiceField(
         label=_("Can see posts likes"),
         label=_("Can see posts likes"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Number only")), (2, _("Number and list of likers")))
+        choices=((0, _("No")), (1, _("Number only")), (2, _("Number and list of likers")), ),
     )
     )
     can_like_posts = YesNoSwitch(
     can_like_posts = YesNoSwitch(
         label=_("Can like posts"),
         label=_("Can like posts"),
-        help_text=_("Only users with this permission to see likes can like posts.")
+        help_text=_("Only users with this permission to see likes can like posts."),
     )
     )
 
 
     can_protect_posts = YesNoSwitch(
     can_protect_posts = YesNoSwitch(
         label=_("Can protect posts"),
         label=_("Can protect posts"),
-        help_text=_("Only users with this permission can edit protected posts.")
+        help_text=_("Only users with this permission can edit protected posts."),
     )
     )
     can_move_posts = YesNoSwitch(
     can_move_posts = YesNoSwitch(
         label=_("Can move posts"), help_text=_("Will be able to move posts to other threads.")
         label=_("Can move posts"), help_text=_("Will be able to move posts to other threads.")
@@ -180,7 +180,7 @@ class CategoryPermissionsForm(forms.Form):
     can_merge_posts = YesNoSwitch(label=_("Can merge posts"))
     can_merge_posts = YesNoSwitch(label=_("Can merge posts"))
     can_approve_content = YesNoSwitch(
     can_approve_content = YesNoSwitch(
         label=_("Can approve content"),
         label=_("Can approve content"),
-        help_text=_("Will be able to see and approve unapproved content.")
+        help_text=_("Will be able to see and approve unapproved content."),
     )
     )
     can_report_content = YesNoSwitch(label=_("Can report posts"))
     can_report_content = YesNoSwitch(label=_("Can report posts"))
     can_see_reports = YesNoSwitch(label=_("Can see reports"))
     can_see_reports = YesNoSwitch(label=_("Can see reports"))
@@ -189,7 +189,7 @@ class CategoryPermissionsForm(forms.Form):
         label=_("Can hide events"),
         label=_("Can hide events"),
         coerce=int,
         coerce=int,
         initial=0,
         initial=0,
-        choices=((0, _("No")), (1, _("Hide events")), (2, _("Delete events")))
+        choices=((0, _("No")), (1, _("Hide events")), (2, _("Delete events")), ),
     )
     )
 
 
 
 
@@ -476,8 +476,10 @@ ACL tests
 
 
 def allow_see_thread(user, target):
 def allow_see_thread(user, target):
     category_acl = user.acl_cache['categories'].get(
     category_acl = user.acl_cache['categories'].get(
-        target.category_id, {'can_see': False,
-                             'can_browse': False}
+        target.category_id, {
+            'can_see': False,
+            'can_browse': False,
+        }
     )
     )
 
 
     if not (category_acl['can_see'] and category_acl['can_browse']):
     if not (category_acl['can_see'] and category_acl['can_browse']):
@@ -502,8 +504,10 @@ def allow_start_thread(user, target):
         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_cache['categories'].get(
-        target.pk, {'can_close_threads': False,
-                    'can_start_threads': False}
+        target.pk, {
+            'can_close_threads': False,
+            'can_start_threads': False,
+        }
     )
     )
 
 
     if target.is_closed and not category_acl['can_close_threads']:
     if target.is_closed and not category_acl['can_close_threads']:
@@ -523,8 +527,10 @@ def allow_reply_thread(user, target):
         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_cache['categories'].get(
-        target.category_id, {'can_close_threads': False,
-                             'can_reply_threads': False}
+        target.category_id, {
+            'can_close_threads': False,
+            'can_reply_threads': False,
+        }
     )
     )
 
 
     if not category_acl['can_close_threads']:
     if not category_acl['can_close_threads']:
@@ -575,8 +581,10 @@ can_edit_thread = return_boolean(allow_edit_thread)
 
 
 def allow_see_post(user, target):
 def allow_see_post(user, target):
     category_acl = user.acl_cache['categories'].get(
     category_acl = user.acl_cache['categories'].get(
-        target.category_id, {'can_approve_content': False,
-                             'can_hide_events': False}
+        target.category_id, {
+            'can_approve_content': False,
+            'can_hide_events': False,
+        }
     )
     )
 
 
     if not target.is_event and target.is_unapproved:
     if not target.is_event and target.is_unapproved:
@@ -625,7 +633,7 @@ def allow_edit_post(user, target):
             message = ungettext(
             message = ungettext(
                 "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.",
-                category_acl['post_edit_time']
+                category_acl['post_edit_time'],
             )
             )
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
 
 
@@ -638,8 +646,10 @@ def allow_unhide_post(user, target):
         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_cache['categories'].get(
-        target.category_id, {'can_hide_posts': 0,
-                             'can_hide_own_posts': 0}
+        target.category_id, {
+            'can_hide_posts': 0,
+            'can_hide_own_posts': 0,
+        }
     )
     )
 
 
     if not category_acl['can_hide_posts']:
     if not category_acl['can_hide_posts']:
@@ -662,7 +672,7 @@ def allow_unhide_post(user, target):
             message = ungettext(
             message = ungettext(
                 "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.",
-                category_acl['post_edit_time']
+                category_acl['post_edit_time'],
             )
             )
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
 
 
@@ -678,8 +688,10 @@ def allow_hide_post(user, target):
         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_cache['categories'].get(
-        target.category_id, {'can_hide_posts': 0,
-                             'can_hide_own_posts': 0}
+        target.category_id, {
+            'can_hide_posts': 0,
+            'can_hide_own_posts': 0,
+        }
     )
     )
 
 
     if not category_acl['can_hide_posts']:
     if not category_acl['can_hide_posts']:
@@ -702,7 +714,7 @@ def allow_hide_post(user, target):
             message = ungettext(
             message = ungettext(
                 "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.",
-                category_acl['post_edit_time']
+                category_acl['post_edit_time'],
             )
             )
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
 
 
@@ -718,8 +730,10 @@ def allow_delete_post(user, target):
         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_cache['categories'].get(
-        target.category_id, {'can_hide_posts': 0,
-                             'can_hide_own_posts': 0}
+        target.category_id, {
+            'can_hide_posts': 0,
+            'can_hide_own_posts': 0,
+        }
     )
     )
 
 
     if category_acl['can_hide_posts'] != 2:
     if category_acl['can_hide_posts'] != 2:
@@ -742,7 +756,7 @@ def allow_delete_post(user, target):
             message = ungettext(
             message = ungettext(
                 "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.",
-                category_acl['post_edit_time']
+                category_acl['post_edit_time'],
             )
             )
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
             raise PermissionDenied(message % {'minutes': category_acl['post_edit_time']})
 
 

+ 5 - 3
misago/threads/search.py

@@ -33,7 +33,7 @@ class SearchThreads(SearchProvider):
             page,
             page,
             settings.MISAGO_POSTS_PER_PAGE,
             settings.MISAGO_POSTS_PER_PAGE,
             settings.MISAGO_POSTS_TAIL,
             settings.MISAGO_POSTS_TAIL,
-            allow_explicit_first_page=True
+            allow_explicit_first_page=True,
         )
         )
         paginator = pagination_dict(list_page)
         paginator = pagination_dict(list_page)
 
 
@@ -46,7 +46,9 @@ class SearchThreads(SearchProvider):
         add_categories_to_items(root_category.unwrap(), threads_categories, posts + threads)
         add_categories_to_items(root_category.unwrap(), threads_categories, posts + threads)
 
 
         results = {
         results = {
-            'results': FeedSerializer(posts, many=True, context={'user': self.request.user}).data
+            'results': FeedSerializer(posts, many=True, context={
+                'user': self.request.user,
+            }).data
         }
         }
         results.update(paginator)
         results.update(paginator)
 
 
@@ -62,5 +64,5 @@ def search_threads(request, query, visible_threads):
         is_hidden=False,
         is_hidden=False,
         is_unapproved=False,
         is_unapproved=False,
         thread_id__in=visible_threads.values('id'),
         thread_id__in=visible_threads.values('id'),
-        search_vector=search_query
+        search_vector=search_query,
     ).annotate(rank=SearchRank(search_vector, search_query)).order_by('-rank', '-id')
     ).annotate(rank=SearchRank(search_vector, search_query)).order_by('-rank', '-id')

+ 4 - 1
misago/threads/serializers/feed.py

@@ -29,7 +29,10 @@ class FeedSerializer(PostSerializer, MutableFields):
         fields = PostSerializer.Meta.fields + ['category', 'thread', 'top_category']
         fields = PostSerializer.Meta.fields + ['category', 'thread', 'top_category']
 
 
     def get_thread(self, obj):
     def get_thread(self, obj):
-        return {'title': obj.thread.title, 'url': obj.thread.get_absolute_url()}
+        return {
+            'title': obj.thread.title,
+            'url': obj.thread.get_absolute_url(),
+        }
 
 
     def get_top_category(self, obj):
     def get_top_category(self, obj):
         try:
         try:

+ 13 - 5
misago/threads/serializers/poll.py

@@ -91,7 +91,10 @@ class EditPollSerializer(serializers.ModelSerializer):
                 choices_map[choice['hash']].update({'label': choice['label']})
                 choices_map[choice['hash']].update({'label': choice['label']})
                 final_choices.append(choices_map[choice['hash']])
                 final_choices.append(choices_map[choice['hash']])
             else:
             else:
-                choice.update({'hash': get_random_string(12), 'votes': 0})
+                choice.update({
+                    'hash': get_random_string(12),
+                    'votes': 0,
+                })
                 final_choices.append(choice)
                 final_choices.append(choice)
 
 
         self.validate_choices_num(final_choices)
         self.validate_choices_num(final_choices)
@@ -120,11 +123,13 @@ class EditPollSerializer(serializers.ModelSerializer):
             message = ungettext(
             message = ungettext(
                 "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).",
                 "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).",
                 "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).",
                 "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).",
-                MAX_POLL_OPTIONS
+                MAX_POLL_OPTIONS,
             )
             )
             raise serializers.ValidationError(
             raise serializers.ValidationError(
-                message % {'limit_value': MAX_POLL_OPTIONS,
-                           'show_value': total_choices}
+                message % {
+                    'limit_value': MAX_POLL_OPTIONS,
+                    'show_value': total_choices,
+                }
             )
             )
 
 
     def validate(self, data):
     def validate(self, data):
@@ -166,7 +171,10 @@ class NewPollSerializer(EditPollSerializer):
         self.validate_choices_num(clean_choices)
         self.validate_choices_num(clean_choices)
 
 
         for choice in clean_choices:
         for choice in clean_choices:
-            choice.update({'hash': get_random_string(12), 'votes': 0})
+            choice.update({
+                'hash': get_random_string(12),
+                'votes': 0,
+            })
 
 
         return clean_choices
         return clean_choices
 
 

+ 8 - 4
misago/threads/serializers/post.py

@@ -150,8 +150,10 @@ class PostSerializer(serializers.ModelSerializer, MutableFields):
     def get_last_editor_url(self, obj):
     def get_last_editor_url(self, obj):
         if obj.last_editor_id:
         if obj.last_editor_id:
             return reverse(
             return reverse(
-                'misago:user', kwargs={'pk': obj.last_editor_id,
-                                       'slug': obj.last_editor_slug}
+                'misago:user', kwargs={
+                    'pk': obj.last_editor_id,
+                    'slug': obj.last_editor_slug,
+                }
             )
             )
         else:
         else:
             return None
             return None
@@ -159,8 +161,10 @@ class PostSerializer(serializers.ModelSerializer, MutableFields):
     def get_hidden_by_url(self, obj):
     def get_hidden_by_url(self, obj):
         if obj.hidden_by_id:
         if obj.hidden_by_id:
             return reverse(
             return reverse(
-                'misago:user', kwargs={'pk': obj.hidden_by_id,
-                                       'slug': obj.hidden_by_slug}
+                'misago:user', kwargs={
+                    'pk': obj.hidden_by_id,
+                    'slug': obj.hidden_by_slug,
+                }
             )
             )
         else:
         else:
             return None
             return None

+ 2 - 2
misago/threads/serializers/thread.py

@@ -87,8 +87,8 @@ class ThreadSerializer(serializers.ModelSerializer, MutableFields):
                 'index': obj.get_posts_api_url(),
                 'index': obj.get_posts_api_url(),
                 'merge': obj.get_post_merge_api_url(),
                 'merge': obj.get_post_merge_api_url(),
                 'move': obj.get_post_move_api_url(),
                 'move': obj.get_post_move_api_url(),
-                'split': obj.get_post_split_api_url()
-            }
+                'split': obj.get_post_split_api_url(),
+            },
         }
         }
 
 
     def get_url(self, obj):
     def get_url(self, obj):

+ 25 - 10
misago/threads/signals.py

@@ -23,7 +23,10 @@ move_thread = Signal()
 @receiver(merge_thread)
 @receiver(merge_thread)
 def merge_threads_posts(sender, **kwargs):
 def merge_threads_posts(sender, **kwargs):
     other_thread = kwargs['other_thread']
     other_thread = kwargs['other_thread']
-    other_thread.post_set.update(category=sender.category, thread=sender)
+    other_thread.post_set.update(
+        category=sender.category,
+        thread=sender,
+    )
 
 
 
 
 @receiver(merge_post)
 @receiver(merge_post)
@@ -99,35 +102,47 @@ def delete_user_threads(sender, **kwargs):
 @receiver(username_changed)
 @receiver(username_changed)
 def update_usernames(sender, **kwargs):
 def update_usernames(sender, **kwargs):
     Thread.objects.filter(starter=sender).update(
     Thread.objects.filter(starter=sender).update(
-        starter_name=sender.username, starter_slug=sender.slug
+        starter_name=sender.username,
+        starter_slug=sender.slug,
     )
     )
 
 
     Thread.objects.filter(last_poster=sender).update(
     Thread.objects.filter(last_poster=sender).update(
-        last_poster_name=sender.username, last_poster_slug=sender.slug
+        last_poster_name=sender.username,
+        last_poster_slug=sender.slug,
     )
     )
 
 
-    Post.objects.filter(poster=sender).update(poster_name=sender.username)
+    Post.objects.filter(poster=sender).update(
+        poster_name=sender.username,
+    )
 
 
     Post.objects.filter(last_editor=sender).update(
     Post.objects.filter(last_editor=sender).update(
-        last_editor_name=sender.username, last_editor_slug=sender.slug
+        last_editor_name=sender.username,
+        last_editor_slug=sender.slug,
     )
     )
 
 
     PostEdit.objects.filter(editor=sender).update(
     PostEdit.objects.filter(editor=sender).update(
-        editor_name=sender.username, editor_slug=sender.slug
+        editor_name=sender.username,
+        editor_slug=sender.slug,
     )
     )
 
 
     PostLike.objects.filter(liker=sender).update(
     PostLike.objects.filter(liker=sender).update(
-        liker_name=sender.username, liker_slug=sender.slug
+        liker_name=sender.username,
+        liker_slug=sender.slug,
     )
     )
 
 
     Attachment.objects.filter(uploader=sender).update(
     Attachment.objects.filter(uploader=sender).update(
-        uploader_name=sender.username, uploader_slug=sender.slug
+        uploader_name=sender.username,
+        uploader_slug=sender.slug,
     )
     )
 
 
-    Poll.objects.filter(poster=sender).update(poster_name=sender.username, poster_slug=sender.slug)
+    Poll.objects.filter(poster=sender).update(
+        poster_name=sender.username,
+        poster_slug=sender.slug,
+    )
 
 
     PollVote.objects.filter(voter=sender).update(
     PollVote.objects.filter(voter=sender).update(
-        voter_name=sender.username, voter_slug=sender.slug
+        voter_name=sender.username,
+        voter_slug=sender.slug,
     )
     )
 
 
 
 

+ 3 - 1
misago/threads/subscriptions.py

@@ -21,7 +21,9 @@ def make_threads_subscription_aware(user, threads):
             thread.subscription = None
             thread.subscription = None
             threads_dict[thread.pk] = thread
             threads_dict[thread.pk] = thread
 
 
-        subscriptions_queryset = user.subscription_set.filter(thread_id__in=threads_dict.keys())
+        subscriptions_queryset = user.subscription_set.filter(
+            thread_id__in=threads_dict.keys(),
+        )
 
 
         for subscription in subscriptions_queryset.iterator():
         for subscription in subscriptions_queryset.iterator():
             threads_dict[subscription.thread_id].subscription = subscription
             threads_dict[subscription.thread_id].subscription = subscription

+ 2 - 1
misago/threads/templatetags/misago_poststags.py

@@ -32,7 +32,8 @@ def likes_label(post):
 
 
     return ngettext(
     return ngettext(
         "%(users)s and %(likes)s other user like this.",
         "%(users)s and %(likes)s other user like this.",
-        "%(users)s and %(likes)s other users like this.", hidden_likes
+        "%(users)s and %(likes)s other users like this.",
+        hidden_likes,
     ) % formats
     ) % formats
 
 
 
 

+ 42 - 15
misago/threads/tests/test_attachmentadmin_views.py

@@ -30,7 +30,7 @@ class AttachmentAdminViewsTests(AdminTestCase):
             filename='testfile_{}.zip'.format(Attachment.objects.count() + 1),
             filename='testfile_{}.zip'.format(Attachment.objects.count() + 1),
             file=None,
             file=None,
             image=None,
             image=None,
-            thumbnail=None
+            thumbnail=None,
         )
         )
 
 
     def test_link_registered(self):
     def test_link_registered(self):
@@ -48,7 +48,11 @@ class AttachmentAdminViewsTests(AdminTestCase):
         attachments = [
         attachments = [
             self.mock_attachment(self.post, file='somefile.pdf'),
             self.mock_attachment(self.post, file='somefile.pdf'),
             self.mock_attachment(image='someimage.jpg'),
             self.mock_attachment(image='someimage.jpg'),
-            self.mock_attachment(self.post, image='somelargeimage.png', thumbnail='somethumb.png'),
+            self.mock_attachment(
+                self.post,
+                image='somelargeimage.png',
+                thumbnail='somethumb.png',
+            ),
         ]
         ]
 
 
         response = self.client.get(final_link)
         response = self.client.get(final_link)
@@ -56,7 +60,9 @@ class AttachmentAdminViewsTests(AdminTestCase):
 
 
         for attachment in attachments:
         for attachment in attachments:
             delete_link = reverse(
             delete_link = reverse(
-                'misago:admin:system:attachments:delete', kwargs={'pk': attachment.pk}
+                'misago:admin:system:attachments:delete', kwargs={
+                    'pk': attachment.pk,
+                }
             )
             )
             self.assertContains(response, attachment.filename)
             self.assertContains(response, attachment.filename)
             self.assertContains(response, delete_link)
             self.assertContains(response, delete_link)
@@ -72,7 +78,11 @@ class AttachmentAdminViewsTests(AdminTestCase):
         attachments = [
         attachments = [
             self.mock_attachment(self.post, file='somefile.pdf'),
             self.mock_attachment(self.post, file='somefile.pdf'),
             self.mock_attachment(image='someimage.jpg'),
             self.mock_attachment(image='someimage.jpg'),
-            self.mock_attachment(self.post, image='somelargeimage.png', thumbnail='somethumb.png'),
+            self.mock_attachment(
+                self.post,
+                image='somelargeimage.png',
+                thumbnail='somethumb.png',
+            ),
         ]
         ]
 
 
         self.post.attachments_cache = [{'id': attachments[-1].pk}]
         self.post.attachments_cache = [{'id': attachments[-1].pk}]
@@ -80,8 +90,10 @@ class AttachmentAdminViewsTests(AdminTestCase):
 
 
         response = self.client.post(
         response = self.client.post(
             self.admin_link,
             self.admin_link,
-            data={'action': 'delete',
-                  'selected_items': [a.pk for a in attachments]}
+            data={
+                'action': 'delete',
+                'selected_items': [a.pk for a in attachments],
+            }
         )
         )
         self.assertEqual(response.status_code, 302)
         self.assertEqual(response.status_code, 302)
 
 
@@ -94,17 +106,23 @@ class AttachmentAdminViewsTests(AdminTestCase):
     def test_delete_view(self):
     def test_delete_view(self):
         """delete attachment view has no showstoppers"""
         """delete attachment view has no showstoppers"""
         attachment = self.mock_attachment(self.post)
         attachment = self.mock_attachment(self.post)
-        self.post.attachments_cache = [{
-            'id': attachment.pk + 1
-        }, {
-            'id': attachment.pk
-        }, {
-            'id': attachment.pk + 2
-        }]
+        self.post.attachments_cache = [
+            {
+                'id': attachment.pk + 1
+            },
+            {
+                'id': attachment.pk
+            },
+            {
+                'id': attachment.pk + 2
+            },
+        ]
         self.post.save()
         self.post.save()
 
 
         action_link = reverse(
         action_link = reverse(
-            'misago:admin:system:attachments:delete', kwargs={'pk': attachment.pk}
+            'misago:admin:system:attachments:delete', kwargs={
+                'pk': attachment.pk,
+            }
         )
         )
 
 
         response = self.client.post(action_link)
         response = self.client.post(action_link)
@@ -119,4 +137,13 @@ class AttachmentAdminViewsTests(AdminTestCase):
 
 
         # assert it was removed from post's attachments cache
         # assert it was removed from post's attachments cache
         attachments_cache = self.category.post_set.get(pk=self.post.pk).attachments_cache
         attachments_cache = self.category.post_set.get(pk=self.post.pk).attachments_cache
-        self.assertEqual(attachments_cache, [{'id': attachment.pk + 1}, {'id': attachment.pk + 2}])
+        self.assertEqual(
+            attachments_cache, [
+                {
+                    'id': attachment.pk + 1,
+                },
+                {
+                    'id': attachment.pk + 2,
+                },
+            ]
+        )

+ 96 - 24
misago/threads/tests/test_attachments_api.py

@@ -55,33 +55,53 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_invalid_extension(self):
     def test_invalid_extension(self):
         """uploaded file's extension is rejected as invalid"""
         """uploaded file's extension is rejected as invalid"""
-        AttachmentType.objects.create(name="Test extension", extensions='jpg,jpeg', mimetypes=None)
+        AttachmentType.objects.create(
+            name="Test extension",
+            extensions='jpg,jpeg',
+            mimetypes=None,
+        )
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
 
 
     def test_invalid_mime(self):
     def test_invalid_mime(self):
         """uploaded file's mimetype is rejected as invalid"""
         """uploaded file's mimetype is rejected as invalid"""
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='png', mimetypes='loremipsum'
+            name="Test extension",
+            extensions='png',
+            mimetypes='loremipsum',
         )
         )
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
 
 
     def test_no_perm_to_type(self):
     def test_no_perm_to_type(self):
         """user needs permission to upload files of this type"""
         """user needs permission to upload files of this type"""
         attachment_type = AttachmentType.objects.create(
         attachment_type = AttachmentType.objects.create(
-            name="Test extension", extensions='png', mimetypes='application/pdf'
+            name="Test extension",
+            extensions='png',
+            mimetypes='application/pdf',
         )
         )
 
 
         user_roles = (r.pk for r in self.user.get_roles())
         user_roles = (r.pk for r in self.user.get_roles())
         attachment_type.limit_uploads_to.set(Role.objects.exclude(id__in=user_roles))
         attachment_type.limit_uploads_to.set(Role.objects.exclude(id__in=user_roles))
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
 
 
     def test_type_is_locked(self):
     def test_type_is_locked(self):
@@ -90,11 +110,15 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
             name="Test extension",
             name="Test extension",
             extensions='png',
             extensions='png',
             mimetypes='application/pdf',
             mimetypes='application/pdf',
-            status=AttachmentType.LOCKED
+            status=AttachmentType.LOCKED,
         )
         )
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
 
 
     def test_type_is_disabled(self):
     def test_type_is_disabled(self):
@@ -103,21 +127,32 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
             name="Test extension",
             name="Test extension",
             extensions='png',
             extensions='png',
             mimetypes='application/pdf',
             mimetypes='application/pdf',
-            status=AttachmentType.DISABLED
+            status=AttachmentType.DISABLED,
         )
         )
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
         self.assertContains(response, "You can't upload files of this type.", status_code=400)
 
 
     def test_upload_too_big_for_type(self):
     def test_upload_too_big_for_type(self):
         """too big uploads are rejected"""
         """too big uploads are rejected"""
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='png', mimetypes='image/png', size_limit=100
+            name="Test extension",
+            extensions='png',
+            mimetypes='image/png',
+            size_limit=100,
         )
         )
 
 
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
 
 
         self.assertContains(
         self.assertContains(
             response, "can't upload files of this type larger than", status_code=400
             response, "can't upload files of this type larger than", status_code=400
@@ -128,29 +163,48 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.override_acl({'max_attachment_size': 100})
         self.override_acl({'max_attachment_size': 100})
 
 
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='png', mimetypes='image/png'
+            name="Test extension",
+            extensions='png',
+            mimetypes='image/png',
         )
         )
 
 
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "can't upload files larger than", status_code=400)
         self.assertContains(response, "can't upload files larger than", status_code=400)
 
 
     def test_corrupted_image_upload(self):
     def test_corrupted_image_upload(self):
         """corrupted image upload is handled"""
         """corrupted image upload is handled"""
-        AttachmentType.objects.create(name="Test extension", extensions='gif')
+        AttachmentType.objects.create(
+            name="Test extension",
+            extensions='gif',
+        )
 
 
         with open(TEST_CORRUPTEDIMG_PATH, 'rb') as upload:
         with open(TEST_CORRUPTEDIMG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertContains(response, "Uploaded image was corrupted or invalid.", status_code=400)
         self.assertContains(response, "Uploaded image was corrupted or invalid.", status_code=400)
 
 
     def test_document_upload(self):
     def test_document_upload(self):
         """successful upload creates orphan attachment"""
         """successful upload creates orphan attachment"""
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='pdf', mimetypes='application/pdf'
+            name="Test extension",
+            extensions='pdf',
+            mimetypes='application/pdf',
         )
         )
 
 
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -181,11 +235,17 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
     def test_small_image_upload(self):
     def test_small_image_upload(self):
         """successful small image upload creates orphan attachment without thumbnail"""
         """successful small image upload creates orphan attachment without thumbnail"""
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='jpeg,jpg', mimetypes='image/jpeg'
+            name="Test extension",
+            extensions='jpeg,jpg',
+            mimetypes='image/jpeg',
         )
         )
 
 
         with open(TEST_SMALLJPG_PATH, 'rb') as upload:
         with open(TEST_SMALLJPG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -212,11 +272,17 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.override_acl({'max_attachment_size': 10 * 1024})
         self.override_acl({'max_attachment_size': 10 * 1024})
 
 
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='png', mimetypes='image/png'
+            name="Test extension",
+            extensions='png',
+            mimetypes='image/png',
         )
         )
 
 
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
         with open(TEST_LARGEPNG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -259,11 +325,17 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
     def test_animated_image_upload(self):
     def test_animated_image_upload(self):
         """successful gif upload creates orphan attachment with thumbnail"""
         """successful gif upload creates orphan attachment with thumbnail"""
         AttachmentType.objects.create(
         AttachmentType.objects.create(
-            name="Test extension", extensions='gif', mimetypes='image/gif'
+            name="Test extension",
+            extensions='gif',
+            mimetypes='image/gif',
         )
         )
 
 
         with open(TEST_ANIMATEDGIF_PATH, 'rb') as upload:
         with open(TEST_ANIMATEDGIF_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()

+ 16 - 10
misago/threads/tests/test_attachments_middleware.py

@@ -66,7 +66,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
                 request=RequestMock(test_input),
                 request=RequestMock(test_input),
                 mode=PostingEndpoint.START,
                 mode=PostingEndpoint.START,
                 user=self.user,
                 user=self.user,
-                post=self.post
+                post=self.post,
             )
             )
 
 
             serializer = middleware.get_serializer()
             serializer = middleware.get_serializer()
@@ -83,7 +83,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
                 }),
                 }),
                 mode=PostingEndpoint.START,
                 mode=PostingEndpoint.START,
                 user=self.user,
                 user=self.user,
-                post=self.post
+                post=self.post,
             )
             )
 
 
             serializer = middleware.get_serializer()
             serializer = middleware.get_serializer()
@@ -92,7 +92,10 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
     def test_get_initial_attachments(self):
     def test_get_initial_attachments(self):
         """get_initial_attachments returns list of attachments already existing on post"""
         """get_initial_attachments returns list of attachments already existing on post"""
         middleware = AttachmentsMiddleware(
         middleware = AttachmentsMiddleware(
-            request=RequestMock(), mode=PostingEndpoint.EDIT, user=self.user, post=self.post
+            request=RequestMock(),
+            mode=PostingEndpoint.EDIT,
+            user=self.user,
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()
@@ -111,7 +114,10 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
     def test_get_new_attachments(self):
     def test_get_new_attachments(self):
         """get_initial_attachments returns list of attachments already existing on post"""
         """get_initial_attachments returns list of attachments already existing on post"""
         middleware = AttachmentsMiddleware(
         middleware = AttachmentsMiddleware(
-            request=RequestMock(), mode=PostingEndpoint.EDIT, user=self.user, post=self.post
+            request=RequestMock(),
+            mode=PostingEndpoint.EDIT,
+            user=self.user,
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()
@@ -132,7 +138,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
         """middleware validates if we have permission to delete other users attachments"""
         """middleware validates if we have permission to delete other users attachments"""
         self.override_acl({
         self.override_acl({
             'max_attachment_size': 1024,
             'max_attachment_size': 1024,
-            'can_delete_other_users_attachments': False
+            'can_delete_other_users_attachments': False,
         })
         })
 
 
         attachment = self.mock_attachment(user=False, post=self.post)
         attachment = self.mock_attachment(user=False, post=self.post)
@@ -144,7 +150,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
             }),
             }),
             mode=PostingEndpoint.EDIT,
             mode=PostingEndpoint.EDIT,
             user=self.user,
             user=self.user,
-            post=self.post
+            post=self.post,
         ).get_serializer()
         ).get_serializer()
 
 
         self.assertFalse(serializer.is_valid())
         self.assertFalse(serializer.is_valid())
@@ -162,7 +168,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
             }),
             }),
             mode=PostingEndpoint.EDIT,
             mode=PostingEndpoint.EDIT,
             user=self.user,
             user=self.user,
-            post=self.post
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()
@@ -190,7 +196,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
             }),
             }),
             mode=PostingEndpoint.EDIT,
             mode=PostingEndpoint.EDIT,
             user=self.user,
             user=self.user,
-            post=self.post
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()
@@ -222,7 +228,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
             }),
             }),
             mode=PostingEndpoint.EDIT,
             mode=PostingEndpoint.EDIT,
             user=self.user,
             user=self.user,
-            post=self.post
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()
@@ -250,7 +256,7 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
             }),
             }),
             mode=PostingEndpoint.EDIT,
             mode=PostingEndpoint.EDIT,
             user=self.user,
             user=self.user,
-            post=self.post
+            post=self.post,
         )
         )
 
 
         serializer = middleware.get_serializer()
         serializer = middleware.get_serializer()

+ 9 - 3
misago/threads/tests/test_attachmenttypeadmin_views.py

@@ -72,7 +72,9 @@ class AttachmentTypeAdminViewsTests(AdminTestCase):
         self.assertEqual(test_type.name, 'Test type')
         self.assertEqual(test_type.name, 'Test type')
 
 
         form_link = reverse(
         form_link = reverse(
-            'misago:admin:system:attachment-types:edit', kwargs={'pk': test_type.pk}
+            'misago:admin:system:attachment-types:edit', kwargs={
+                'pk': test_type.pk,
+            }
         )
         )
 
 
         response = self.client.get(form_link)
         response = self.client.get(form_link)
@@ -166,7 +168,9 @@ class AttachmentTypeAdminViewsTests(AdminTestCase):
         self.assertEqual(test_type.name, 'Test type')
         self.assertEqual(test_type.name, 'Test type')
 
 
         action_link = reverse(
         action_link = reverse(
-            'misago:admin:system:attachment-types:delete', kwargs={'pk': test_type.pk}
+            'misago:admin:system:attachment-types:delete', kwargs={
+                'pk': test_type.pk,
+            }
         )
         )
 
 
         response = self.client.post(action_link)
         response = self.client.post(action_link)
@@ -205,7 +209,9 @@ class AttachmentTypeAdminViewsTests(AdminTestCase):
         )
         )
 
 
         action_link = reverse(
         action_link = reverse(
-            'misago:admin:system:attachment-types:delete', kwargs={'pk': test_type.pk}
+            'misago:admin:system:attachment-types:delete', kwargs={
+                'pk': test_type.pk,
+            }
         )
         )
 
 
         response = self.client.post(action_link)
         response = self.client.post(action_link)

+ 31 - 11
misago/threads/tests/test_attachmentview.py

@@ -27,8 +27,14 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
 
 
         self.api_link = reverse('misago:api:attachment-list')
         self.api_link = reverse('misago:api:attachment-list')
 
 
-        self.attachment_type_jpg = AttachmentType.objects.create(name="JPG", extensions='jpeg,jpg')
-        self.attachment_type_pdf = AttachmentType.objects.create(name="PDF", extensions='pdf')
+        self.attachment_type_jpg = AttachmentType.objects.create(
+            name="JPG",
+            extensions='jpeg,jpg',
+        )
+        self.attachment_type_pdf = AttachmentType.objects.create(
+            name="PDF",
+            extensions='pdf',
+        )
 
 
         self.override_acl()
         self.override_acl()
 
 
@@ -36,13 +42,17 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
         acl = self.user.acl_cache.copy()
         acl = self.user.acl_cache.copy()
         acl.update({
         acl.update({
             'max_attachment_size': 1000,
             'max_attachment_size': 1000,
-            'can_download_other_users_attachments': allow_download
+            'can_download_other_users_attachments': allow_download,
         })
         })
         override_acl(self.user, acl)
         override_acl(self.user, acl)
 
 
     def upload_document(self, is_orphaned=False, by_other_user=False):
     def upload_document(self, is_orphaned=False, by_other_user=False):
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
         with open(TEST_DOCUMENT_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         attachment = Attachment.objects.order_by('id').last()
         attachment = Attachment.objects.order_by('id').last()
@@ -60,7 +70,11 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
 
 
     def upload_image(self):
     def upload_image(self):
         with open(TEST_SMALLJPG_PATH, 'rb') as upload:
         with open(TEST_SMALLJPG_PATH, 'rb') as upload:
-            response = self.client.post(self.api_link, data={'upload': upload})
+            response = self.client.post(
+                self.api_link, data={
+                    'upload': upload,
+                }
+            )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         attachment = Attachment.objects.order_by('id').last()
         attachment = Attachment.objects.order_by('id').last()
@@ -85,8 +99,10 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
     def test_nonexistant_file(self):
     def test_nonexistant_file(self):
         """user tries to retrieve nonexistant file"""
         """user tries to retrieve nonexistant file"""
         response = self.client.get(
         response = self.client.get(
-            reverse('misago:attachment', kwargs={'pk': 123,
-                                                 'secret': 'qwertyuiop'})
+            reverse('misago:attachment', kwargs={
+                'pk': 123,
+                'secret': 'qwertyuiop',
+            })
         )
         )
 
 
         self.assertIs404(response)
         self.assertIs404(response)
@@ -96,8 +112,10 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
         attachment = self.upload_document()
         attachment = self.upload_document()
 
 
         response = self.client.get(
         response = self.client.get(
-            reverse('misago:attachment', kwargs={'pk': attachment.pk,
-                                                 'secret': 'qwertyuiop'})
+            reverse('misago:attachment', kwargs={
+                'pk': attachment.pk,
+                'secret': 'qwertyuiop',
+            })
         )
         )
 
 
         self.assertIs404(response)
         self.assertIs404(response)
@@ -128,8 +146,10 @@ class AttachmentViewTestCase(AuthenticatedUserTestCase):
         response = self.client.get(
         response = self.client.get(
             reverse(
             reverse(
                 'misago:attachment-thumbnail',
                 'misago:attachment-thumbnail',
-                kwargs={'pk': attachment.pk,
-                        'secret': attachment.secret}
+                kwargs={
+                    'pk': attachment.pk,
+                    'secret': attachment.secret,
+                }
             )
             )
         )
         )
         self.assertIs404(response)
         self.assertIs404(response)

+ 51 - 18
misago/threads/tests/test_emailnotification_middleware.py

@@ -25,12 +25,15 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
 
 
         self.category = Category.objects.get(slug='first-category')
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(
         self.thread = testutils.post_thread(
-            category=self.category, started_on=timezone.now() - timedelta(seconds=5)
+            category=self.category,
+            started_on=timezone.now() - timedelta(seconds=5),
         )
         )
         self.override_acl()
         self.override_acl()
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
         self.other_user = UserModel.objects.create_user('Bob', 'bob@boberson.com', 'pass123')
         self.other_user = UserModel.objects.create_user('Bob', 'bob@boberson.com', 'pass123')
@@ -42,7 +45,7 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 1,
             'can_start_threads': 1,
             'can_reply_threads': 1,
             'can_reply_threads': 1,
-            'can_edit_posts': 1
+            'can_edit_posts': 1,
         })
         })
 
 
         override_acl(self.user, new_acl)
         override_acl(self.user, new_acl)
@@ -54,17 +57,23 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 1,
             'can_start_threads': 1,
             'can_reply_threads': 1,
             'can_reply_threads': 1,
-            'can_edit_posts': 1
+            'can_edit_posts': 1,
         })
         })
 
 
         if hide:
         if hide:
-            new_acl['categories'][self.category.pk].update({'can_browse': False})
+            new_acl['categories'][self.category.pk].update({
+                'can_browse': False,
+            })
 
 
         override_acl(self.other_user, new_acl)
         override_acl(self.other_user, new_acl)
 
 
     def test_no_subscriptions(self):
     def test_no_subscriptions(self):
         """no emails are sent because noone subscibes to thread"""
         """no emails are sent because noone subscibes to thread"""
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 0)
         self.assertEqual(len(mail.outbox), 0)
@@ -75,10 +84,14 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=timezone.now(),
             last_read_on=timezone.now(),
-            send_email=True
+            send_email=True,
         )
         )
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 0)
         self.assertEqual(len(mail.outbox), 0)
@@ -89,10 +102,14 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=timezone.now(),
             last_read_on=timezone.now(),
-            send_email=False
+            send_email=False,
         )
         )
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 0)
         self.assertEqual(len(mail.outbox), 0)
@@ -103,11 +120,15 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=timezone.now(),
             last_read_on=timezone.now(),
-            send_email=True
+            send_email=True,
         )
         )
         self.override_other_user_acl(hide=True)
         self.override_other_user_acl(hide=True)
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 0)
         self.assertEqual(len(mail.outbox), 0)
@@ -118,13 +139,17 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=timezone.now(),
             last_read_on=timezone.now(),
-            send_email=True
+            send_email=True,
         )
         )
         self.override_other_user_acl()
         self.override_other_user_acl()
 
 
         testutils.reply_thread(self.thread, posted_on=timezone.now())
         testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 0)
         self.assertEqual(len(mail.outbox), 0)
@@ -135,11 +160,15 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=timezone.now(),
             last_read_on=timezone.now(),
-            send_email=True
+            send_email=True,
         )
         )
         self.override_other_user_acl()
         self.override_other_user_acl()
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(len(mail.outbox), 1)
@@ -163,11 +192,15 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
             thread=self.thread,
             thread=self.thread,
             category=self.category,
             category=self.category,
             last_read_on=self.thread.last_post_on,
             last_read_on=self.thread.last_post_on,
-            send_email=True
+            send_email=True,
         )
         )
         self.override_other_user_acl()
         self.override_other_user_acl()
 
 
-        response = self.client.post(self.api_link, data={'post': 'This is test response!'})
+        response = self.client.post(
+            self.api_link, data={
+                'post': 'This is test response!',
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(len(mail.outbox), 1)
         self.assertEqual(len(mail.outbox), 1)

+ 1 - 1
misago/threads/tests/test_events.py

@@ -32,7 +32,7 @@ class EventsAPITests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.thread.set_title("Test thread")
         self.thread.set_title("Test thread")

+ 14 - 4
misago/threads/tests/test_floodprotection.py

@@ -18,7 +18,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         self.override_acl()
         self.override_acl()
 
 
         self.post_link = reverse(
         self.post_link = reverse(
-            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def override_acl(self):
     def override_acl(self):
@@ -27,17 +29,25 @@ class PostMentionsTests(AuthenticatedUserTestCase):
             'can_see': 1,
             'can_see': 1,
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 1,
             'can_start_threads': 1,
-            'can_reply_threads': 1
+            'can_reply_threads': 1,
         })
         })
 
 
         override_acl(self.user, new_acl)
         override_acl(self.user, new_acl)
 
 
     def test_flood_has_no_showstoppers(self):
     def test_flood_has_no_showstoppers(self):
         """endpoint handles posting interruption"""
         """endpoint handles posting interruption"""
-        response = self.client.post(self.post_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.post_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        response = self.client.post(self.post_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.post_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "You can't post message so quickly after previous one.", status_code=403
             response, "You can't post message so quickly after previous one.", status_code=403
         )
         )

+ 26 - 8
misago/threads/tests/test_participants.py

@@ -23,7 +23,7 @@ class ParticipantsTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.thread.set_title("Test thread")
         self.thread.set_title("Test thread")
@@ -38,7 +38,7 @@ class ParticipantsTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.first_post = post
         self.thread.first_post = post
@@ -137,7 +137,10 @@ class ParticipantsTests(TestCase):
 
 
         set_users_unread_private_threads_sync(users=users)
         set_users_unread_private_threads_sync(users=users)
         for user in users:
         for user in users:
-            UserModel.objects.get(pk=user.pk, sync_unread_private_threads=True)
+            UserModel.objects.get(
+                pk=user.pk,
+                sync_unread_private_threads=True,
+            )
 
 
     def test_set_participants_unread_private_threads_sync(self):
     def test_set_participants_unread_private_threads_sync(self):
         """
         """
@@ -153,7 +156,10 @@ class ParticipantsTests(TestCase):
 
 
         set_users_unread_private_threads_sync(participants=participants)
         set_users_unread_private_threads_sync(participants=participants)
         for user in users:
         for user in users:
-            UserModel.objects.get(pk=user.pk, sync_unread_private_threads=True)
+            UserModel.objects.get(
+                pk=user.pk,
+                sync_unread_private_threads=True,
+            )
 
 
     def test_set_participants_users_unread_private_threads_sync(self):
     def test_set_participants_users_unread_private_threads_sync(self):
         """
         """
@@ -168,9 +174,15 @@ class ParticipantsTests(TestCase):
 
 
         users.append(UserModel.objects.create_user("Bob2", "bob2@boberson.com", "Pass.123"))
         users.append(UserModel.objects.create_user("Bob2", "bob2@boberson.com", "Pass.123"))
 
 
-        set_users_unread_private_threads_sync(users=users, participants=participants)
+        set_users_unread_private_threads_sync(
+            users=users,
+            participants=participants,
+        )
         for user in users:
         for user in users:
-            UserModel.objects.get(pk=user.pk, sync_unread_private_threads=True)
+            UserModel.objects.get(
+                pk=user.pk,
+                sync_unread_private_threads=True,
+            )
 
 
     def test_set_users_unread_private_threads_sync_exclude_user(self):
     def test_set_users_unread_private_threads_sync_exclude_user(self):
         """exclude_user kwarg works"""
         """exclude_user kwarg works"""
@@ -179,7 +191,10 @@ class ParticipantsTests(TestCase):
             UserModel.objects.create_user("Bob2", "bob2@boberson.com", "Pass.123")
             UserModel.objects.create_user("Bob2", "bob2@boberson.com", "Pass.123")
         ]
         ]
 
 
-        set_users_unread_private_threads_sync(users=users, exclude_user=users[0])
+        set_users_unread_private_threads_sync(
+            users=users,
+            exclude_user=users[0],
+        )
 
 
         self.assertFalse(UserModel.objects.get(pk=users[0].pk).sync_unread_private_threads)
         self.assertFalse(UserModel.objects.get(pk=users[0].pk).sync_unread_private_threads)
         self.assertTrue(UserModel.objects.get(pk=users[1].pk).sync_unread_private_threads)
         self.assertTrue(UserModel.objects.get(pk=users[1].pk).sync_unread_private_threads)
@@ -189,6 +204,9 @@ class ParticipantsTests(TestCase):
         user = UserModel.objects.create_user("Bob1", "bob1@boberson.com", "Pass.123")
         user = UserModel.objects.create_user("Bob1", "bob1@boberson.com", "Pass.123")
 
 
         with self.assertNumQueries(0):
         with self.assertNumQueries(0):
-            set_users_unread_private_threads_sync(users=[user], exclude_user=user)
+            set_users_unread_private_threads_sync(
+                users=[user],
+                exclude_user=user,
+            )
 
 
         self.assertFalse(UserModel.objects.get(pk=user.pk).sync_unread_private_threads)
         self.assertFalse(UserModel.objects.get(pk=user.pk).sync_unread_private_threads)

+ 50 - 15
misago/threads/tests/test_post_mentions.py

@@ -24,7 +24,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         self.override_acl()
         self.override_acl()
 
 
         self.post_link = reverse(
         self.post_link = reverse(
-            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def override_acl(self):
     def override_acl(self):
@@ -34,18 +36,26 @@ class PostMentionsTests(AuthenticatedUserTestCase):
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 1,
             'can_start_threads': 1,
             'can_reply_threads': 1,
             'can_reply_threads': 1,
-            'can_edit_posts': 1
+            'can_edit_posts': 1,
         })
         })
 
 
         override_acl(self.user, new_acl)
         override_acl(self.user, new_acl)
 
 
-    def put(self, url, data=None):
+    def put(
+            self,
+            url,
+            data=None,
+    ):
         content = encode_multipart(BOUNDARY, data or {})
         content = encode_multipart(BOUNDARY, data or {})
         return self.client.put(url, content, content_type=MULTIPART_CONTENT)
         return self.client.put(url, content, content_type=MULTIPART_CONTENT)
 
 
     def test_mention_noone(self):
     def test_mention_noone(self):
         """endpoint handles no mentions in post"""
         """endpoint handles no mentions in post"""
-        response = self.client.post(self.post_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.post_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         post = self.user.post_set.order_by('id').last()
         post = self.user.post_set.order_by('id').last()
@@ -54,7 +64,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
     def test_mention_nonexistant(self):
     def test_mention_nonexistant(self):
         """endpoint handles nonexistant mention"""
         """endpoint handles nonexistant mention"""
         response = self.client.post(
         response = self.client.post(
-            self.post_link, data={'post': "This is test response, @InvalidUser!"}
+            self.post_link, data={
+                'post': "This is test response, @InvalidUser!",
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -64,7 +76,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
     def test_mention_self(self):
     def test_mention_self(self):
         """endpoint mentions author"""
         """endpoint mentions author"""
         response = self.client.post(
         response = self.client.post(
-            self.post_link, data={'post': "This is test response, @{}!".format(self.user)}
+            self.post_link, data={
+                'post': "This is test response, @{}!".format(self.user),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -86,7 +100,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         mentions = ['@{}'.format(u) for u in users]
         mentions = ['@{}'.format(u) for u in users]
         response = self.client.post(
         response = self.client.post(
             self.post_link,
             self.post_link,
-            data={'post': "This is test response, {}!".format(', '.join(mentions))}
+            data={
+                'post': "This is test response, {}!".format(', '.join(mentions)),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -101,7 +117,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         user_b = UserModel.objects.create_user('MentionB', 'mentionb@test.com', 'pass123')
         user_b = UserModel.objects.create_user('MentionB', 'mentionb@test.com', 'pass123')
 
 
         response = self.client.post(
         response = self.client.post(
-            self.post_link, data={'post': "This is test response, @{}!".format(user_a)}
+            self.post_link, data={
+                'post': "This is test response, @{}!".format(user_a),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -112,13 +130,18 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
 
         # add mention to post
         # add mention to post
         edit_link = reverse(
         edit_link = reverse(
-            'misago:api:thread-post-detail', kwargs={'thread_pk': self.thread.pk,
-                                                     'pk': post.pk}
+            'misago:api:thread-post-detail', kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': post.pk,
+            }
         )
         )
 
 
         self.override_acl()
         self.override_acl()
         response = self.put(
         response = self.put(
-            edit_link, data={'post': "This is test response, @{} and @{}!".format(user_a, user_b)}
+            edit_link,
+            data={
+                'post': "This is test response, @{} and @{}!".format(user_a, user_b),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -127,7 +150,11 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
 
         # remove first mention from post - should preserve mentions
         # remove first mention from post - should preserve mentions
         self.override_acl()
         self.override_acl()
-        response = self.put(edit_link, data={'post': "This is test response, @{}!".format(user_b)})
+        response = self.put(
+            edit_link, data={
+                'post': "This is test response, @{}!".format(user_b),
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(post.mentions.count(), 2)
         self.assertEqual(post.mentions.count(), 2)
@@ -135,7 +162,11 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
 
         # remove mentions from post - should preserve mentions
         # remove mentions from post - should preserve mentions
         self.override_acl()
         self.override_acl()
-        response = self.put(edit_link, data={'post': "This is test response!"})
+        response = self.put(
+            edit_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         self.assertEqual(post.mentions.count(), 2)
         self.assertEqual(post.mentions.count(), 2)
@@ -147,7 +178,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
         user_b = UserModel.objects.create_user('MentionB', 'mentionb@test.com', 'pass123')
         user_b = UserModel.objects.create_user('MentionB', 'mentionb@test.com', 'pass123')
 
 
         response = self.client.post(
         response = self.client.post(
-            self.post_link, data={'post': "This is test response, @{}!".format(user_a)}
+            self.post_link, data={
+                'post': "This is test response, @{}!".format(user_a),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -162,7 +195,9 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
 
         response = self.client.post(
         response = self.client.post(
             self.post_link,
             self.post_link,
-            data={'post': "This is test response, @{} and @{}!".format(user_a, user_b)}
+            data={
+                'post': "This is test response, @{} and @{}!".format(user_a, user_b),
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 

+ 5 - 5
misago/threads/tests/test_post_model.py

@@ -26,7 +26,7 @@ class PostModelTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.thread.set_title("Test thread")
         self.thread.set_title("Test thread")
@@ -42,7 +42,7 @@ class PostModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         update_post_checksum(self.post)
         update_post_checksum(self.post)
@@ -67,7 +67,7 @@ class PostModelTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=timezone.now(),
             last_post_on=timezone.now(),
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         # can't merge with other users posts
         # can't merge with other users posts
@@ -134,7 +134,7 @@ class PostModelTests(TestCase):
             parsed="<p>I am other message!</p>",
             parsed="<p>I am other message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=timezone.now() + timedelta(minutes=5),
             posted_on=timezone.now() + timedelta(minutes=5),
-            updated_on=timezone.now() + timedelta(minutes=5)
+            updated_on=timezone.now() + timedelta(minutes=5),
         )
         )
 
 
         other_post.merge(self.post)
         other_post.merge(self.post)
@@ -152,7 +152,7 @@ class PostModelTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=timezone.now(),
             last_post_on=timezone.now(),
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.post.move(new_thread)
         self.post.move(new_thread)

+ 228 - 152
misago/threads/tests/test_privatethread_patch_api.py

@@ -34,11 +34,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -49,7 +51,15 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         """path validates username"""
         """path validates username"""
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'participants', 'value': ''}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': '',
+                },
+            ]
+        )
 
 
         self.assertContains(
         self.assertContains(
             response, "You have to enter new participant's username.", status_code=400
             response, "You have to enter new participant's username.", status_code=400
@@ -60,11 +70,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': 'InvalidUser'
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': 'InvalidUser',
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "No user with such name exists.", status_code=400)
         self.assertContains(response, "No user with such name exists.", status_code=400)
@@ -74,11 +86,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "This user is already thread participant", status_code=400)
         self.assertContains(response, "This user is already thread participant", status_code=400)
@@ -89,11 +103,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         self.other_user.blocks.add(self.user)
         self.other_user.blocks.add(self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "BobBoberson is blocking you.", status_code=400)
         self.assertContains(response, "BobBoberson is blocking you.", status_code=400)
@@ -105,11 +121,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.other_user, {'can_use_private_threads': 0})
         override_acl(self.other_user, {'can_use_private_threads': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "BobBoberson can't participate", status_code=400)
         self.assertContains(response, "BobBoberson can't participate", status_code=400)
@@ -125,11 +143,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
             ThreadParticipant.objects.add_participants(self.thread, [user])
             ThreadParticipant.objects.add_participants(self.thread, [user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -144,11 +164,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -160,11 +182,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         self.patch(
         self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         # event was set on thread
         # event was set on thread
@@ -189,11 +213,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': 1})
         override_acl(self.user, {'can_moderate_private_threads': 1})
 
 
         self.patch(
         self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.user.username,
+                },
+            ]
         )
         )
 
 
         # event was set on thread
         # event was set on thread
@@ -214,11 +240,13 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': 1})
         override_acl(self.user, {'can_moderate_private_threads': 1})
 
 
         self.patch(
         self.patch(
-            self.api_link, [{
-                'op': 'add',
-                'path': 'participants',
-                'value': self.other_user.username
-            }]
+            self.api_link, [
+                {
+                    'op': 'add',
+                    'path': 'participants',
+                    'value': self.other_user.username,
+                },
+            ]
         )
         )
 
 
         # event was set on thread
         # event was set on thread
@@ -240,11 +268,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': 'string'
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': 'string',
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
@@ -254,11 +284,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': 'string'
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': 'string',
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
@@ -268,11 +300,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
@@ -283,11 +317,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -303,11 +339,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -325,11 +363,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         )
         )
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -363,11 +403,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -399,11 +441,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': True})
         override_acl(self.user, {'can_moderate_private_threads': True})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': removed_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': removed_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -432,11 +476,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -464,11 +510,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -495,11 +543,13 @@ class PrivateThreadRemoveParticipantApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'remove',
-                'path': 'participants',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'remove',
+                    'path': 'participants',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -518,7 +568,15 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         """api handles empty user id"""
         """api handles empty user id"""
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'owner', 'value': ''}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': '',
+                },
+            ]
+        )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
 
 
@@ -527,11 +585,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': 'dsadsa'
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': 'dsadsa',
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
@@ -541,11 +601,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
         self.assertContains(response, "Participant doesn't exist.", status_code=400)
@@ -556,11 +618,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
         ThreadParticipant.objects.add_participants(self.thread, [self.user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -573,11 +637,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(response, "This user already is thread owner.", status_code=400)
         self.assertContains(response, "This user already is thread owner.", status_code=400)
@@ -591,11 +657,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertContains(
         self.assertContains(
@@ -608,11 +676,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.other_user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.other_user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -641,11 +711,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': 1})
         override_acl(self.user, {'can_moderate_private_threads': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': new_owner.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': new_owner.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -674,11 +746,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': 1})
         override_acl(self.user, {'can_moderate_private_threads': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -708,11 +782,13 @@ class PrivateThreadTakeOverApiTests(PrivateThreadPatchApiTestCase):
         override_acl(self.user, {'can_moderate_private_threads': 1})
         override_acl(self.user, {'can_moderate_private_threads': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'owner',
-                'value': self.user.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'owner',
+                    'value': self.user.pk,
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 5 - 1
misago/threads/tests/test_privatethread_reply_api.py

@@ -25,7 +25,11 @@ class PrivateThreadReplyApiTestCase(PrivateThreadsTestCase):
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
         ThreadParticipant.objects.add_participants(self.thread, [self.other_user])
 
 
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # don't count private thread replies
         # don't count private thread replies

+ 44 - 21
misago/threads/tests/test_privatethread_start_api.py

@@ -56,7 +56,7 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
             response.json(), {
             response.json(), {
                 'to': ["You have to enter user names."],
                 'to': ["You have to enter user names."],
                 'title': ["You have to enter thread title."],
                 'title': ["You have to enter thread title."],
-                'post': ["You have to enter a message."]
+                'post': ["You have to enter a message."],
             }
             }
         )
         )
 
 
@@ -73,7 +73,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
     def test_post_is_validated(self):
     def test_post_is_validated(self):
@@ -89,8 +91,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'post': ["Posted message should be at least 5 characters long (it has 1)."]}
+            response.json(), {
+                'post': ["Posted message should be at least 5 characters long (it has 1)."],
+            }
         )
         )
 
 
     def test_cant_invite_self(self):
     def test_cant_invite_self(self):
@@ -106,8 +109,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'to': ["You can't include yourself on the list of users to invite to new thread."]}
+            response.json(), {
+                'to': ["You can't include yourself on the list of users to invite to new thread."],
+            }
         )
         )
 
 
     def test_cant_invite_nonexisting(self):
     def test_cant_invite_nonexisting(self):
@@ -122,7 +126,11 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
         )
         )
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'to': ["One or more users could not be found: ab, cd"]})
+        self.assertEqual(
+            response.json(), {
+                'to': ["One or more users could not be found: ab, cd"],
+            }
+        )
 
 
     def test_cant_invite_too_many(self):
     def test_cant_invite_too_many(self):
         """api validates that you cant invite too many users to thread"""
         """api validates that you cant invite too many users to thread"""
@@ -137,8 +145,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'to': ["You can't add more than 3 users to private thread (you've added 50)."]}
+            response.json(), {
+                'to': ["You can't add more than 3 users to private thread (you've added 50)."],
+            }
         )
         )
 
 
     def test_cant_invite_no_permission(self):
     def test_cant_invite_no_permission(self):
@@ -156,7 +165,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'to': ["BobBoberson can't participate in private threads."]}
+            response.json(), {
+                'to': ["BobBoberson can't participate in private threads."],
+            }
         )
         )
 
 
     def test_cant_invite_blocking(self):
     def test_cant_invite_blocking(self):
@@ -173,7 +184,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
         )
         )
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'to': ["BobBoberson is blocking you."]})
+        self.assertEqual(response.json(), {
+            'to': ["BobBoberson is blocking you."],
+        })
 
 
         # allow us to bypass blocked check
         # allow us to bypass blocked check
         override_acl(self.user, {'can_add_everyone_to_private_threads': 1})
         override_acl(self.user, {'can_add_everyone_to_private_threads': 1})
@@ -189,7 +202,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
     def test_cant_invite_followers_only(self):
     def test_cant_invite_followers_only(self):
@@ -209,8 +224,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'to': ["BobBoberson limits invitations to private threads to followed users."]}
+            response.json(), {
+                'to': ["BobBoberson limits invitations to private threads to followed users."],
+            }
         )
         )
 
 
         # allow us to bypass following check
         # allow us to bypass following check
@@ -227,7 +243,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
         # make user follow us
         # make user follow us
@@ -245,7 +263,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
     def test_cant_invite_anyone(self):
     def test_cant_invite_anyone(self):
@@ -265,8 +285,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'to': ["BobBoberson is not allowing invitations to private threads."]}
+            response.json(), {
+                'to': ["BobBoberson is not allowing invitations to private threads."],
+            }
         )
         )
 
 
         # allow us to bypass user preference check
         # allow us to bypass user preference check
@@ -283,7 +304,9 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
     def test_can_start_thread(self):
     def test_can_start_thread(self):
@@ -293,7 +316,7 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
             data={
             data={
                 'to': [self.other_user.username],
                 'to': [self.other_user.username],
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
-                'post': "Lorem ipsum dolor met!"
+                'post': "Lorem ipsum dolor met!",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -361,7 +384,7 @@ class StartPrivateThreadTests(AuthenticatedUserTestCase):
             data={
             data={
                 'to': [self.other_user.username],
                 'to': [self.other_user.username],
                 'title': "Brzęczyżczykiewicz",
                 'title': "Brzęczyżczykiewicz",
-                'post': "Chrzążczyżewoszyce, powiat Łękółody."
+                'post': "Chrzążczyżewoszyce, powiat Łękółody.",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 11 - 3
misago/threads/tests/test_privatethreads.py

@@ -8,7 +8,11 @@ class PrivateThreadsTestCase(AuthenticatedUserTestCase):
         super(PrivateThreadsTestCase, self).setUp()
         super(PrivateThreadsTestCase, self).setUp()
         self.category = Category.objects.private_threads()
         self.category = Category.objects.private_threads()
 
 
-        override_acl(self.user, {'can_use_private_threads': 1, 'can_start_private_threads': 1})
+        override_acl(self.user, {
+            'can_use_private_threads': 1,
+            'can_start_private_threads': 1,
+        })
+
         self.override_acl()
         self.override_acl()
 
 
     def override_acl(self, acl=None):
     def override_acl(self, acl=None):
@@ -23,10 +27,14 @@ class PrivateThreadsTestCase(AuthenticatedUserTestCase):
             'can_edit_posts': 0,
             'can_edit_posts': 0,
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
-            'can_merge_threads': 0
+            'can_merge_threads': 0,
         })
         })
 
 
         if acl:
         if acl:
             final_acl.update(acl)
             final_acl.update(acl)
 
 
-        override_acl(self.user, {'categories': {self.category.pk: final_acl}})
+        override_acl(self.user, {
+            'categories': {
+                self.category.pk: final_acl,
+            },
+        })

+ 18 - 14
misago/threads/tests/test_privatethreads_api.py

@@ -118,13 +118,15 @@ class PrivateThreadRetrieveApiTests(PrivateThreadsTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['title'], self.thread.title)
         self.assertEqual(response_json['title'], self.thread.title)
         self.assertEqual(
         self.assertEqual(
-            response_json['participants'], [{
-                'id': self.user.id,
-                'username': self.user.username,
-                'avatars': self.user.avatars,
-                'url': self.user.get_absolute_url(),
-                'is_owner': True
-            }]
+            response_json['participants'], [
+                {
+                    'id': self.user.id,
+                    'username': self.user.username,
+                    'avatars': self.user.avatars,
+                    'url': self.user.get_absolute_url(),
+                    'is_owner': True,
+                },
+            ]
         )
         )
 
 
     def test_can_see_participant(self):
     def test_can_see_participant(self):
@@ -137,13 +139,15 @@ class PrivateThreadRetrieveApiTests(PrivateThreadsTestCase):
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(response_json['title'], self.thread.title)
         self.assertEqual(response_json['title'], self.thread.title)
         self.assertEqual(
         self.assertEqual(
-            response_json['participants'], [{
-                'id': self.user.id,
-                'username': self.user.username,
-                'avatars': self.user.avatars,
-                'url': self.user.get_absolute_url(),
-                'is_owner': False
-            }]
+            response_json['participants'], [
+                {
+                    'id': self.user.id,
+                    'username': self.user.username,
+                    'avatars': self.user.avatars,
+                    'url': self.user.get_absolute_url(),
+                    'is_owner': False,
+                },
+            ]
         )
         )
 
 
     def test_mod_can_see_reported(self):
     def test_mod_can_see_reported(self):

+ 15 - 3
misago/threads/tests/test_search.py

@@ -82,7 +82,11 @@ class SearchApiTests(AuthenticatedUserTestCase):
     def test_hidden_post(self):
     def test_hidden_post(self):
         """hidden posts are extempt from search"""
         """hidden posts are extempt from search"""
         thread = testutils.post_thread(self.category)
         thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.", is_hidden=True)
+        post = testutils.reply_thread(
+            thread,
+            message="Lorem ipsum dolor.",
+            is_hidden=True,
+        )
         self.index_post(post)
         self.index_post(post)
 
 
         response = self.client.get('%s?q=ipsum' % self.api_link)
         response = self.client.get('%s?q=ipsum' % self.api_link)
@@ -98,7 +102,11 @@ class SearchApiTests(AuthenticatedUserTestCase):
     def test_unapproved_post(self):
     def test_unapproved_post(self):
         """unapproves posts are extempt from search"""
         """unapproves posts are extempt from search"""
         thread = testutils.post_thread(self.category)
         thread = testutils.post_thread(self.category)
-        post = testutils.reply_thread(thread, message="Lorem ipsum dolor.", is_unapproved=True)
+        post = testutils.reply_thread(
+            thread,
+            message="Lorem ipsum dolor.",
+            is_unapproved=True,
+        )
         self.index_post(post)
         self.index_post(post)
 
 
         response = self.client.get('%s?q=ipsum' % self.api_link)
         response = self.client.get('%s?q=ipsum' % self.api_link)
@@ -172,4 +180,8 @@ class SearchProviderApiTests(SearchApiTests):
     def setUp(self):
     def setUp(self):
         super(SearchProviderApiTests, self).setUp()
         super(SearchProviderApiTests, self).setUp()
 
 
-        self.api_link = reverse('misago:api:search', kwargs={'search_provider': 'threads'})
+        self.api_link = reverse(
+            'misago:api:search', kwargs={
+                'search_provider': 'threads',
+            }
+        )

+ 32 - 10
misago/threads/tests/test_subscription_middleware.py

@@ -26,7 +26,7 @@ class SubscriptionMiddlewareTestCase(AuthenticatedUserTestCase):
             'can_see': 1,
             'can_see': 1,
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 1,
             'can_start_threads': 1,
-            'can_reply_threads': 1
+            'can_reply_threads': 1,
         })
         })
 
 
         override_acl(self.user, new_acl)
         override_acl(self.user, new_acl)
@@ -48,7 +48,7 @@ class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
             data={
             data={
                 'category': self.category.id,
                 'category': self.category.id,
                 'title': "This is an test thread!",
                 'title': "This is an test thread!",
-                'post': "This is test response!"
+                'post': "This is test response!",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -66,7 +66,7 @@ class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
             data={
             data={
                 'category': self.category.id,
                 'category': self.category.id,
                 'title': "This is an test thread!",
                 'title': "This is an test thread!",
-                'post': "This is test response!"
+                'post': "This is test response!",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -88,7 +88,7 @@ class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
             data={
             data={
                 'category': self.category.id,
                 'category': self.category.id,
                 'title': "This is an test thread!",
                 'title': "This is an test thread!",
-                'post': "This is test response!"
+                'post': "This is test response!",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -106,7 +106,9 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         super(SubscribeRepliedThreadTests, self).setUp()
         super(SubscribeRepliedThreadTests, self).setUp()
         self.thread = testutils.post_thread(self.category)
         self.thread = testutils.post_thread(self.category)
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def test_dont_subscribe(self):
     def test_dont_subscribe(self):
@@ -115,7 +117,11 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_NONE
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_NONE
         self.user.save()
         self.user.save()
 
 
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # user has no subscriptions
         # user has no subscriptions
@@ -126,7 +132,11 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_NOTIFY
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_NOTIFY
         self.user.save()
         self.user.save()
 
 
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # user has subscribed to thread
         # user has subscribed to thread
@@ -140,7 +150,11 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_ALL
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_ALL
         self.user.save()
         self.user.save()
 
 
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # user has subscribed to thread
         # user has subscribed to thread
@@ -154,13 +168,21 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_ALL
         self.user.subscribe_to_replied_threads = UserModel.SUBSCRIBE_ALL
         self.user.save()
         self.user.save()
 
 
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # clear subscription
         # clear subscription
         self.user.subscription_set.all().delete()
         self.user.subscription_set.all().delete()
         # reply again
         # reply again
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         # user has no subscriptions
         # user has no subscriptions

+ 4 - 1
misago/threads/tests/test_subscriptions.py

@@ -22,7 +22,10 @@ class SubscriptionsTests(TestCase):
         self.anon = AnonymousUser()
         self.anon = AnonymousUser()
 
 
     def post_thread(self, datetime):
     def post_thread(self, datetime):
-        return testutils.post_thread(category=self.category, started_on=datetime)
+        return testutils.post_thread(
+            category=self.category,
+            started_on=datetime,
+        )
 
 
     def test_anon_subscription(self):
     def test_anon_subscription(self):
         """make single thread sub aware for anon"""
         """make single thread sub aware for anon"""

+ 32 - 11
misago/threads/tests/test_thread_editreply_api.py

@@ -20,8 +20,10 @@ class EditReplyTests(AuthenticatedUserTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def override_acl(self, extra_acl=None):
     def override_acl(self, extra_acl=None):
@@ -31,7 +33,7 @@ class EditReplyTests(AuthenticatedUserTestCase):
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 0,
             'can_start_threads': 0,
             'can_reply_threads': 0,
             'can_reply_threads': 0,
-            'can_edit_posts': 1
+            'can_edit_posts': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -144,7 +146,9 @@ class EditReplyTests(AuthenticatedUserTestCase):
         response = self.put(self.api_link, data={})
         response = self.put(self.api_link, data={})
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'post': ["You have to enter a message."]})
+        self.assertEqual(response.json(), {
+            'post': ["You have to enter a message."],
+        })
 
 
     def test_edit_event(self):
     def test_edit_event(self):
         """events can't be edited"""
         """events can't be edited"""
@@ -169,8 +173,9 @@ class EditReplyTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'post': ["Posted message should be at least 5 characters long (it has 1)."]}
+            response.json(), {
+                'post': ["Posted message should be at least 5 characters long (it has 1)."],
+            }
         )
         )
 
 
     def test_edit_reply_no_change(self):
     def test_edit_reply_no_change(self):
@@ -234,8 +239,10 @@ class EditReplyTests(AuthenticatedUserTestCase):
 
 
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.thread.first_post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.thread.first_post.pk,
+            }
         )
         )
 
 
         response = self.put(api_link, data={'post': "This is test edit!"})
         response = self.put(api_link, data={'post': "This is test edit!"})
@@ -245,7 +252,12 @@ class EditReplyTests(AuthenticatedUserTestCase):
         """can protect post"""
         """can protect post"""
         self.override_acl({'can_protect_posts': 1})
         self.override_acl({'can_protect_posts': 1})
 
 
-        response = self.put(self.api_link, data={'post': "Lorem ipsum dolor met!", 'protect': 1})
+        response = self.put(
+            self.api_link, data={
+                'post': "Lorem ipsum dolor met!",
+                'protect': 1,
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         post = self.user.post_set.order_by('id').last()
         post = self.user.post_set.order_by('id').last()
@@ -255,7 +267,12 @@ class EditReplyTests(AuthenticatedUserTestCase):
         """cant protect post without permission"""
         """cant protect post without permission"""
         self.override_acl({'can_protect_posts': 0})
         self.override_acl({'can_protect_posts': 0})
 
 
-        response = self.put(self.api_link, data={'post': "Lorem ipsum dolor met!", 'protect': 1})
+        response = self.put(
+            self.api_link, data={
+                'post': "Lorem ipsum dolor met!",
+                'protect': 1,
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         post = self.user.post_set.order_by('id').last()
         post = self.user.post_set.order_by('id').last()
@@ -265,5 +282,9 @@ class EditReplyTests(AuthenticatedUserTestCase):
         """unicode characters can be posted"""
         """unicode characters can be posted"""
         self.override_acl()
         self.override_acl()
 
 
-        response = self.put(self.api_link, data={'post': "Chrzążczyżewoszyce, powiat Łękółody."})
+        response = self.put(
+            self.api_link, data={
+                'post': "Chrzążczyżewoszyce, powiat Łękółody.",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 76 - 24
misago/threads/tests/test_thread_merge_api.py

@@ -16,11 +16,17 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             name='Category B',
             name='Category B',
             slug='category-b',
             slug='category-b',
         ).insert_at(
         ).insert_at(
-            self.category, position='last-child', save=True
+            self.category,
+            position='last-child',
+            save=True,
         )
         )
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
 
 
-        self.api_link = reverse('misago:api:thread-merge', kwargs={'pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-merge', kwargs={
+                'pk': self.thread.pk,
+            }
+        )
 
 
     def override_other_acl(self, acl=None):
     def override_other_acl(self, acl=None):
         other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
         other_category_acl = self.user.acl_cache['categories'][self.category.pk].copy()
@@ -34,7 +40,7 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
             'can_edit_posts': 0,
             'can_edit_posts': 0,
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
-            'can_merge_threads': 0
+            'can_merge_threads': 0,
         })
         })
 
 
         if acl:
         if acl:
@@ -76,14 +82,20 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         """api validates thread url"""
         """api validates thread url"""
         self.override_acl({'can_merge_threads': 1})
         self.override_acl({'can_merge_threads': 1})
 
 
-        response = self.client.post(self.api_link, {'thread_url': self.user.get_absolute_url()})
+        response = self.client.post(self.api_link, {
+            'thread_url': self.user.get_absolute_url(),
+        })
         self.assertContains(response, "This is not a valid thread link.", status_code=400)
         self.assertContains(response, "This is not a valid thread link.", status_code=400)
 
 
     def test_current_thread_url(self):
     def test_current_thread_url(self):
         """api validates if thread url given is to current thread"""
         """api validates if thread url given is to current thread"""
         self.override_acl({'can_merge_threads': 1})
         self.override_acl({'can_merge_threads': 1})
 
 
-        response = self.client.post(self.api_link, {'thread_url': self.thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': self.thread.get_absolute_url(),
+            }
+        )
         self.assertContains(response, "You can't merge thread with itself.", status_code=400)
         self.assertContains(response, "You can't merge thread with itself.", status_code=400)
 
 
     def test_other_thread_exists(self):
     def test_other_thread_exists(self):
@@ -96,7 +108,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_thread_url = other_thread.get_absolute_url()
         other_thread_url = other_thread.get_absolute_url()
         other_thread.delete()
         other_thread.delete()
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread_url})
+        response = self.client.post(self.api_link, {
+            'thread_url': other_thread_url,
+        })
         self.assertContains(
         self.assertContains(
             response, "The thread you have entered link to doesn't exist", status_code=400
             response, "The thread you have entered link to doesn't exist", status_code=400
         )
         )
@@ -109,7 +123,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "The thread you have entered link to doesn't exist", status_code=400
             response, "The thread you have entered link to doesn't exist", status_code=400
         )
         )
@@ -122,7 +140,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "You don't have permission to merge this thread", status_code=400
             response, "You don't have permission to merge this thread", status_code=400
         )
         )
@@ -135,7 +157,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "You can't merge this thread into thread you can't reply.", status_code=400
             response, "You can't merge this thread into thread you can't reply.", status_code=400
         )
         )
@@ -148,7 +174,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 
         # other thread has two posts now
         # other thread has two posts now
@@ -167,7 +197,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
         poll = testutils.post_poll(other_thread, self.user)
         poll = testutils.post_poll(other_thread, self.user)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 
         # other thread has two posts now
         # other thread has two posts now
@@ -190,7 +224,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 
         # other thread has two posts now
         # other thread has two posts now
@@ -214,13 +252,19 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(), {
             response.json(), {
-                'polls': [[0, "Delete all polls"],
-                          [poll.pk, poll.question],
-                          [other_poll.pk, other_poll.question]]
+                'polls': [
+                    [0, "Delete all polls"],
+                    [poll.pk, poll.question],
+                    [other_poll.pk, other_poll.question],
+                ]
             }
             }
         )
         )
 
 
@@ -240,8 +284,10 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         testutils.post_poll(other_thread, self.user)
         testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, {'thread_url': other_thread.get_absolute_url(),
-                            'poll': 'jhdkajshdsak'}
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+                'poll': 'jhdkajshdsak',
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.json(), {'detail': "Invalid choice."})
         self.assertEqual(response.json(), {'detail': "Invalid choice."})
@@ -261,8 +307,10 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         testutils.post_poll(other_thread, self.user)
         testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, {'thread_url': other_thread.get_absolute_url(),
-                            'poll': 0}
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+                'poll': 0,
+            }
         )
         )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 
@@ -288,8 +336,10 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, {'thread_url': other_thread.get_absolute_url(),
-                            'poll': poll.pk}
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+                'poll': poll.pk,
+            }
         )
         )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 
@@ -322,8 +372,10 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         other_poll = testutils.post_poll(other_thread, self.user)
         other_poll = testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, {'thread_url': other_thread.get_absolute_url(),
-                            'poll': other_poll.pk}
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+                'poll': other_poll.pk,
+            }
         )
         )
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
         self.assertContains(response, other_thread.get_absolute_url(), status_code=200)
 
 

+ 15 - 13
misago/threads/tests/test_thread_model.py

@@ -23,7 +23,7 @@ class ThreadModelTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.thread.set_title("Test thread")
         self.thread.set_title("Test thread")
@@ -38,7 +38,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.first_post = post
         self.thread.first_post = post
@@ -62,7 +62,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         # first sync call, updates last thread
         # first sync call, updates last thread
@@ -90,7 +90,7 @@ class ThreadModelTests(TestCase):
             checksum="nope",
             checksum="nope",
             posted_on=datetime + timedelta(5),
             posted_on=datetime + timedelta(5),
             updated_on=datetime + timedelta(5),
             updated_on=datetime + timedelta(5),
-            is_unapproved=True
+            is_unapproved=True,
         )
         )
 
 
         self.thread.synchronize()
         self.thread.synchronize()
@@ -116,7 +116,7 @@ class ThreadModelTests(TestCase):
             checksum="nope",
             checksum="nope",
             posted_on=datetime + timedelta(10),
             posted_on=datetime + timedelta(10),
             updated_on=datetime + timedelta(10),
             updated_on=datetime + timedelta(10),
-            is_hidden=True
+            is_hidden=True,
         )
         )
 
 
         self.thread.synchronize()
         self.thread.synchronize()
@@ -174,7 +174,7 @@ class ThreadModelTests(TestCase):
             checksum="nope",
             checksum="nope",
             posted_on=datetime + timedelta(10),
             posted_on=datetime + timedelta(10),
             updated_on=datetime + timedelta(10),
             updated_on=datetime + timedelta(10),
-            is_event=True
+            is_event=True,
         )
         )
 
 
         self.thread.synchronize()
         self.thread.synchronize()
@@ -202,7 +202,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.synchronize()
         self.thread.synchronize()
@@ -225,7 +225,7 @@ class ThreadModelTests(TestCase):
             poster_name='test',
             poster_name='test',
             poster_slug='test',
             poster_slug='test',
             poster_ip='127.0.0.1',
             poster_ip='127.0.0.1',
-            choices=[]
+            choices=[],
         )
         )
 
 
         self.thread.synchronize()
         self.thread.synchronize()
@@ -247,7 +247,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.set_first_post(post)
         self.thread.set_first_post(post)
@@ -273,7 +273,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.set_last_post(post)
         self.thread.set_last_post(post)
@@ -291,7 +291,9 @@ class ThreadModelTests(TestCase):
             name='New Category',
             name='New Category',
             slug='new-category',
             slug='new-category',
         ).insert_at(
         ).insert_at(
-            root_category, position='last-child', save=True
+            root_category,
+            position='last-child',
+            save=True,
         )
         )
         new_category = Category.objects.get(slug='new-category')
         new_category = Category.objects.get(slug='new-category')
 
 
@@ -315,7 +317,7 @@ class ThreadModelTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         other_thread.set_title("Other thread")
         other_thread.set_title("Other thread")
@@ -330,7 +332,7 @@ class ThreadModelTests(TestCase):
             parsed="<p>Hello! I am other message!</p>",
             parsed="<p>Hello! I am other message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         other_thread.first_post = post
         other_thread.first_post = post

+ 265 - 133
misago/threads/tests/test_thread_patch_api.py

@@ -17,7 +17,13 @@ class ThreadPatchApiTestCase(ThreadsApiTestCase):
 class ThreadAddAclApiTests(ThreadPatchApiTestCase):
 class ThreadAddAclApiTests(ThreadPatchApiTestCase):
     def test_add_acl_true(self):
     def test_add_acl_true(self):
         """api adds current thread's acl to response"""
         """api adds current thread's acl to response"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -25,7 +31,13 @@ class ThreadAddAclApiTests(ThreadPatchApiTestCase):
 
 
     def test_add_acl_false(self):
     def test_add_acl_false(self):
         """if value is false, api won't add acl to the response, but will set empty key"""
         """if value is false, api won't add acl to the response, but will set empty key"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': False}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': False,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -38,11 +50,13 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_edit_threads': 2})
         self.override_acl({'can_edit_threads': 2})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'title',
-                'value': "Lorem ipsum change!"
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'title',
+                    'value': "Lorem ipsum change!",
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -54,11 +68,13 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_edit_threads': 0})
         self.override_acl({'can_edit_threads': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'title',
-                'value': "Lorem ipsum change!"
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'title',
+                    'value': "Lorem ipsum change!",
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -74,11 +90,13 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         self.thread.save()
         self.thread.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'title',
-                'value': "Lorem ipsum change!"
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'title',
+                    'value': "Lorem ipsum change!",
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -91,7 +109,15 @@ class ThreadChangeTitleApiTests(ThreadPatchApiTestCase):
         """api cleans, validates and rejects too short title"""
         """api cleans, validates and rejects too short title"""
         self.override_acl({'can_edit_threads': 2})
         self.override_acl({'can_edit_threads': 2})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'title', 'value': 12}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'title',
+                    'value': 12,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -106,7 +132,15 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         """api makes it possible to pin globally thread"""
         """api makes it possible to pin globally thread"""
         self.override_acl({'can_pin_threads': 2})
         self.override_acl({'can_pin_threads': 2})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 2}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 2,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
@@ -122,7 +156,15 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
 
 
         self.override_acl({'can_pin_threads': 2})
         self.override_acl({'can_pin_threads': 2})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 0}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 0,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
@@ -132,7 +174,15 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
         """api pin thread globally with no permission fails"""
         """api pin thread globally with no permission fails"""
         self.override_acl({'can_pin_threads': 1})
         self.override_acl({'can_pin_threads': 1})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 2}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 2,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -153,7 +203,15 @@ class ThreadPinGloballyApiTests(ThreadPatchApiTestCase):
 
 
         self.override_acl({'can_pin_threads': 1})
         self.override_acl({'can_pin_threads': 1})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 1}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 1,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -170,7 +228,15 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         """api makes it possible to pin locally thread"""
         """api makes it possible to pin locally thread"""
         self.override_acl({'can_pin_threads': 1})
         self.override_acl({'can_pin_threads': 1})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 1}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 1,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
@@ -186,7 +252,15 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
 
 
         self.override_acl({'can_pin_threads': 1})
         self.override_acl({'can_pin_threads': 1})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 0}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 0,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         thread_json = self.get_thread_json()
         thread_json = self.get_thread_json()
@@ -196,7 +270,15 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
         """api pin thread locally with no permission fails"""
         """api pin thread locally with no permission fails"""
         self.override_acl({'can_pin_threads': 0})
         self.override_acl({'can_pin_threads': 0})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 1}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 1,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -217,7 +299,15 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
 
 
         self.override_acl({'can_pin_threads': 0})
         self.override_acl({'can_pin_threads': 0})
 
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'weight', 'value': 0}])
+        response = self.patch(
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'weight',
+                    'value': 0,
+                },
+            ]
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -237,7 +327,9 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
             name='Category B',
             name='Category B',
             slug='category-b',
             slug='category-b',
         ).insert_at(
         ).insert_at(
-            self.category, position='last-child', save=True
+            self.category,
+            position='last-child',
+            save=True,
         )
         )
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
 
 
@@ -277,17 +369,17 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk
+                    'value': self.category_b.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
                     'path': 'top-category',
                     'path': 'top-category',
-                    'value': self.category_b.pk
+                    'value': self.category_b.pk,
                 },
                 },
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'flatten-categories',
                     'path': 'flatten-categories',
-                    'value': None
+                    'value': None,
                 },
                 },
             ]
             ]
         )
         )
@@ -312,7 +404,7 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'category',
                     'path': 'category',
-                    'value': self.category_b.pk
+                    'value': self.category_b.pk,
                 },
                 },
                 {
                 {
                     'op': 'add',
                     'op': 'add',
@@ -322,7 +414,7 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'flatten-categories',
                     'path': 'flatten-categories',
-                    'value': None
+                    'value': None,
                 },
                 },
             ]
             ]
         )
         )
@@ -343,11 +435,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.override_other_acl({})
         self.override_other_acl({})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'category',
-                'value': self.category_b.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'category',
+                    'value': self.category_b.pk,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -367,11 +461,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.override_other_acl({'can_see': False})
         self.override_other_acl({'can_see': False})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'category',
-                'value': self.category_b.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'category',
+                    'value': self.category_b.pk,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -389,11 +485,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.override_other_acl({'can_browse': False})
         self.override_other_acl({'can_browse': False})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'category',
-                'value': self.category_b.pk
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'category',
+                    'value': self.category_b.pk,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -414,11 +512,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
         self.override_other_acl({'can_start_threads': 2})
         self.override_other_acl({'can_start_threads': 2})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'category',
-                'value': self.thread.category_id
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'category',
+                    'value': self.thread.category_id,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -435,11 +535,13 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
     def test_thread_flatten_categories(self):
     def test_thread_flatten_categories(self):
         """api flatten thread categories"""
         """api flatten thread categories"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'flatten-categories',
-                'value': None
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'flatten-categories',
+                    'value': None,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -463,7 +565,7 @@ class ThreadMoveApiTests(ThreadPatchApiTestCase):
                 {
                 {
                     'op': 'replace',
                     'op': 'replace',
                     'path': 'flatten-categories',
                     'path': 'flatten-categories',
-                    'value': None
+                    'value': None,
                 },
                 },
             ]
             ]
         )
         )
@@ -480,11 +582,13 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_close_threads': True})
         self.override_acl({'can_close_threads': True})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-closed',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-closed',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -502,11 +606,13 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_close_threads': True})
         self.override_acl({'can_close_threads': True})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-closed',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-closed',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -518,11 +624,13 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_close_threads': False})
         self.override_acl({'can_close_threads': False})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-closed',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-closed',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -545,11 +653,13 @@ class ThreadCloseApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_close_threads': False})
         self.override_acl({'can_close_threads': False})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-closed',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-closed',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -571,11 +681,13 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -587,11 +699,13 @@ class ThreadApproveApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -605,11 +719,13 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_hide_threads': 1})
         self.override_acl({'can_hide_threads': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -631,11 +747,13 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_hide_threads': 1})
         self.override_acl({'can_hide_threads': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -649,11 +767,13 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_hide_threads': 0})
         self.override_acl({'can_hide_threads': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -678,11 +798,13 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
         self.override_acl({'can_hide_threads': 0})
         self.override_acl({'can_hide_threads': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
@@ -691,11 +813,13 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
     def test_subscribe_thread(self):
     def test_subscribe_thread(self):
         """api makes it possible to subscribe thread"""
         """api makes it possible to subscribe thread"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'subscription',
-                'value': 'notify'
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'subscription',
+                    'value': 'notify',
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -709,11 +833,13 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
     def test_subscribe_thread_with_email(self):
     def test_subscribe_thread_with_email(self):
         """api makes it possible to subscribe thread with emails"""
         """api makes it possible to subscribe thread with emails"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'subscription',
-                'value': 'email'
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'subscription',
+                    'value': 'email',
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -727,11 +853,13 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
     def test_unsubscribe_thread(self):
     def test_unsubscribe_thread(self):
         """api makes it possible to unsubscribe thread"""
         """api makes it possible to unsubscribe thread"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'subscription',
-                'value': 'remove'
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'subscription',
+                    'value': 'remove',
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -746,11 +874,13 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
         self.logout_user()
         self.logout_user()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'subscription',
-                'value': 'email'
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'subscription',
+                    'value': 'email',
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
@@ -762,11 +892,13 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
         )
         )
 
 
         response = self.patch(
         response = self.patch(
-            bad_api_link, [{
-                'op': 'replace',
-                'path': 'subscription',
-                'value': 'email'
-            }]
+            bad_api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'subscription',
+                    'value': 'email',
+                },
+            ]
         )
         )
 
 
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)

+ 8 - 4
misago/threads/tests/test_thread_poll_api.py

@@ -17,7 +17,9 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
         self.override_acl()
         self.override_acl()
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-poll-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-poll-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def post(self, url, data=None):
     def post(self, url, data=None):
@@ -39,7 +41,7 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
             'can_edit_polls': 1,
             'can_edit_polls': 1,
             'can_delete_polls': 1,
             'can_delete_polls': 1,
             'poll_edit_time': 0,
             'poll_edit_time': 0,
-            'can_always_see_poll_voters': 0
+            'can_always_see_poll_voters': 0,
         })
         })
 
 
         if user:
         if user:
@@ -54,6 +56,8 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.poll.pk,
+            }
         )
         )

+ 74 - 32
misago/threads/tests/test_thread_pollcreate_api.py

@@ -16,14 +16,22 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
 
 
     def test_invalid_thread_id(self):
     def test_invalid_thread_id(self):
         """api validates that thread id is integer"""
         """api validates that thread id is integer"""
-        api_link = reverse('misago:api:thread-poll-list', kwargs={'thread_pk': 'kjha6dsa687sa'})
+        api_link = reverse(
+            'misago:api:thread-poll-list', kwargs={
+                'thread_pk': 'kjha6dsa687sa',
+            }
+        )
 
 
         response = self.post(api_link)
         response = self.post(api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
 
 
     def test_nonexistant_thread_id(self):
     def test_nonexistant_thread_id(self):
         """api validates that thread exists"""
         """api validates that thread exists"""
-        api_link = reverse('misago:api:thread-poll-list', kwargs={'thread_pk': self.thread.pk + 1})
+        api_link = reverse(
+            'misago:api:thread-poll-list', kwargs={
+                'thread_pk': self.thread.pk + 1,
+            }
+        )
 
 
         response = self.post(api_link)
         response = self.post(api_link)
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
@@ -90,10 +98,12 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
             poster_ip='127.0.0.1',
             poster_ip='127.0.0.1',
             length=30,
             length=30,
             question='Test',
             question='Test',
-            choices=[{
-                'hash': 't3st'
-            }],
-            allowed_choices=1
+            choices=[
+                {
+                    'hash': 't3st'
+                },
+            ],
+            allowed_choices=1,
         )
         )
 
 
         response = self.post(self.api_link)
         response = self.post(self.api_link)
@@ -109,7 +119,11 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
 
 
     def test_length_validation(self):
     def test_length_validation(self):
         """api validates poll's length"""
         """api validates poll's length"""
-        response = self.post(self.api_link, data={'length': -1})
+        response = self.post(
+            self.api_link, data={
+                'length': -1,
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -117,7 +131,11 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
             response_json['length'], ["Ensure this value is greater than or equal to 0."]
             response_json['length'], ["Ensure this value is greater than or equal to 0."]
         )
         )
 
 
-        response = self.post(self.api_link, data={'length': 200})
+        response = self.post(
+            self.api_link, data={
+                'length': 200,
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -127,7 +145,11 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
 
 
     def test_question_validation(self):
     def test_question_validation(self):
         """api validates question length"""
         """api validates question length"""
-        response = self.post(self.api_link, data={'question': 'abcd' * 255})
+        response = self.post(
+            self.api_link, data={
+                'question': 'abcd' * 255,
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -138,10 +160,14 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
     def test_validate_choice_length(self):
     def test_validate_choice_length(self):
         """api validates single choice length"""
         """api validates single choice length"""
         response = self.post(
         response = self.post(
-            self.api_link, data={'choices': [{
-                'hash': 'qwertyuiopas',
-                'label': ''
-            }]}
+            self.api_link, data={
+                'choices': [
+                    {
+                        'hash': 'qwertyuiopas',
+                        'label': '',
+                    },
+                ],
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -149,10 +175,15 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
         self.assertEqual(response_json['choices'], ["One or more poll choices are invalid."])
         self.assertEqual(response_json['choices'], ["One or more poll choices are invalid."])
 
 
         response = self.post(
         response = self.post(
-            self.api_link, data={'choices': [{
-                'hash': 'qwertyuiopas',
-                'label': 'abcd' * 255
-            }]}
+            self.api_link,
+            data={
+                'choices': [
+                    {
+                        'hash': 'qwertyuiopas',
+                        'label': 'abcd' * 255,
+                    },
+                ],
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -172,9 +203,13 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
     def test_validate_max_choices(self):
     def test_validate_max_choices(self):
         """api validates that there are no more choices in poll than allowed number"""
         """api validates that there are no more choices in poll than allowed number"""
         response = self.post(
         response = self.post(
-            self.api_link, data={'choices': [{
-                'label': 'Choice'
-            }] * (MAX_POLL_OPTIONS + 1)}
+            self.api_link, data={
+                'choices': [
+                    {
+                        'label': 'Choice',
+                    },
+                ] * (MAX_POLL_OPTIONS + 1),
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -202,11 +237,14 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
                 'length': 0,
                 'length': 0,
                 'question': "Lorem ipsum",
                 'question': "Lorem ipsum",
                 'allowed_choices': 3,
                 'allowed_choices': 3,
-                'choices': [{
-                    'label': 'Choice'
-                }, {
-                    'label': 'Choice'
-                }]
+                'choices': [
+                    {
+                        'label': 'Choice',
+                    },
+                    {
+                        'label': 'Choice',
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
@@ -227,13 +265,17 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
                 'allowed_choices': 2,
                 'allowed_choices': 2,
                 'allow_revotes': True,
                 'allow_revotes': True,
                 'is_public': True,
                 'is_public': True,
-                'choices': [{
-                    'label': '\nRed '
-                }, {
-                    'label': 'Green'
-                }, {
-                    'label': 'Blue'
-                }]
+                'choices': [
+                    {
+                        'label': '\nRed ',
+                    },
+                    {
+                        'label': 'Green',
+                    },
+                    {
+                        'label': 'Blue',
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 16 - 8
misago/threads/tests/test_thread_polldelete_api.py

@@ -25,8 +25,10 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         """api validates that thread id is integer"""
         """api validates that thread id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': 'kjha6dsa687sa',
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': 'kjha6dsa687sa',
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.client.delete(api_link)
         response = self.client.delete(api_link)
@@ -36,8 +38,10 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         """api validates that thread exists"""
         """api validates that thread exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk + 1,
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': self.thread.pk + 1,
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.client.delete(api_link)
         response = self.client.delete(api_link)
@@ -47,8 +51,10 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         """api validates that poll id is integer"""
         """api validates that poll id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': 'sad98as7d97sa98'}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': 'sad98as7d97sa98',
+            }
         )
         )
 
 
         response = self.client.delete(api_link)
         response = self.client.delete(api_link)
@@ -58,8 +64,10 @@ class ThreadPollDeleteTests(ThreadPollApiTestCase):
         """api validates that poll exists"""
         """api validates that poll exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.poll.pk + 123}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.poll.pk + 123,
+            }
         )
         )
 
 
         response = self.client.delete(api_link)
         response = self.client.delete(api_link)

+ 171 - 107
misago/threads/tests/test_thread_polledit_api.py

@@ -25,8 +25,10 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         """api validates that thread id is integer"""
         """api validates that thread id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': 'kjha6dsa687sa',
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': 'kjha6dsa687sa',
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.put(api_link)
         response = self.put(api_link)
@@ -36,8 +38,10 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         """api validates that thread exists"""
         """api validates that thread exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk + 1,
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': self.thread.pk + 1,
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.put(api_link)
         response = self.put(api_link)
@@ -47,8 +51,10 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         """api validates that poll id is integer"""
         """api validates that poll id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': 'sad98as7d97sa98'}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': 'sad98as7d97sa98',
+            }
         )
         )
 
 
         response = self.put(api_link)
         response = self.put(api_link)
@@ -58,8 +64,10 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         """api validates that poll exists"""
         """api validates that poll exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-detail',
             'misago:api:thread-poll-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.poll.pk + 123}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.poll.pk + 123,
+            }
         )
         )
 
 
         response = self.put(api_link)
         response = self.put(api_link)
@@ -145,7 +153,11 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
 
 
     def test_length_validation(self):
     def test_length_validation(self):
         """api validates poll's length"""
         """api validates poll's length"""
-        response = self.put(self.api_link, data={'length': -1})
+        response = self.put(
+            self.api_link, data={
+                'length': -1,
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -174,10 +186,14 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
     def test_validate_choice_length(self):
     def test_validate_choice_length(self):
         """api validates single choice length"""
         """api validates single choice length"""
         response = self.put(
         response = self.put(
-            self.api_link, data={'choices': [{
-                'hash': 'qwertyuiopas',
-                'label': ''
-            }]}
+            self.api_link, data={
+                'choices': [
+                    {
+                        'hash': 'qwertyuiopas',
+                        'label': '',
+                    },
+                ],
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -185,10 +201,15 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         self.assertEqual(response_json['choices'], ["One or more poll choices are invalid."])
         self.assertEqual(response_json['choices'], ["One or more poll choices are invalid."])
 
 
         response = self.put(
         response = self.put(
-            self.api_link, data={'choices': [{
-                'hash': 'qwertyuiopas',
-                'label': 'abcd' * 255
-            }]}
+            self.api_link,
+            data={
+                'choices': [
+                    {
+                        'hash': 'qwertyuiopas',
+                        'label': 'abcd' * 255,
+                    },
+                ],
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -197,7 +218,15 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
 
 
     def test_validate_two_choices(self):
     def test_validate_two_choices(self):
         """api validates that there are at least two choices in poll"""
         """api validates that there are at least two choices in poll"""
-        response = self.put(self.api_link, data={'choices': [{'label': 'Choice'}]})
+        response = self.put(
+            self.api_link, data={
+                'choices': [
+                    {
+                        'label': 'Choice',
+                    },
+                ],
+            }
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -208,9 +237,13 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
     def test_validate_max_choices(self):
     def test_validate_max_choices(self):
         """api validates that there are no more choices in poll than allowed number"""
         """api validates that there are no more choices in poll than allowed number"""
         response = self.put(
         response = self.put(
-            self.api_link, data={'choices': [{
-                'label': 'Choice'
-            }] * (MAX_POLL_OPTIONS + 1)}
+            self.api_link, data={
+                'choices': [
+                    {
+                        'label': 'Choice',
+                    },
+                ] * (MAX_POLL_OPTIONS + 1),
+            }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -238,11 +271,14 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
                 'length': 0,
                 'length': 0,
                 'question': "Lorem ipsum",
                 'question': "Lorem ipsum",
                 'allowed_choices': 3,
                 'allowed_choices': 3,
-                'choices': [{
-                    'label': 'Choice'
-                }, {
-                    'label': 'Choice'
-                }]
+                'choices': [
+                    {
+                        'label': 'Choice',
+                    },
+                    {
+                        'label': 'Choice',
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
@@ -263,13 +299,17 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
                 'allowed_choices': 2,
                 'allowed_choices': 2,
                 'allow_revotes': True,
                 'allow_revotes': True,
                 'is_public': True,
                 'is_public': True,
-                'choices': [{
-                    'label': '\nRed  '
-                }, {
-                    'label': 'Green'
-                }, {
-                    'label': 'Blue'
-                }]
+                'choices': [
+                    {
+                        'label': '\nRed  ',
+                    },
+                    {
+                        'label': 'Green',
+                    },
+                    {
+                        'label': 'Blue',
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -311,23 +351,28 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
                     True,
                     True,
                 'is_public':
                 'is_public':
                     True,
                     True,
-                'choices': [{
-                    'hash': 'aaaaaaaaaaaa',
-                    'label': '\nFirst  ',
-                    'votes': 5555
-                }, {
-                    'hash': 'bbbbbbbbbbbb',
-                    'label': 'Second',
-                    'votes': 5555
-                }, {
-                    'hash': 'gggggggggggg',
-                    'label': 'Third',
-                    'votes': 5555
-                }, {
-                    'hash': 'dddddddddddd',
-                    'label': 'Fourth',
-                    'votes': 5555
-                }]
+                'choices': [
+                    {
+                        'hash': 'aaaaaaaaaaaa',
+                        'label': '\nFirst  ',
+                        'votes': 5555,
+                    },
+                    {
+                        'hash': 'bbbbbbbbbbbb',
+                        'label': 'Second',
+                        'votes': 5555,
+                    },
+                    {
+                        'hash': 'gggggggggggg',
+                        'label': 'Third',
+                        'votes': 5555,
+                    },
+                    {
+                        'hash': 'dddddddddddd',
+                        'label': 'Fourth',
+                        'votes': 5555,
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -346,27 +391,33 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         # choices were updated
         # choices were updated
         self.assertEqual(len(response_json['choices']), 4)
         self.assertEqual(len(response_json['choices']), 4)
         self.assertEqual(
         self.assertEqual(
-            response_json['choices'], [{
-                'hash': 'aaaaaaaaaaaa',
-                'label': 'First',
-                'votes': 1,
-                'selected': False
-            }, {
-                'hash': 'bbbbbbbbbbbb',
-                'label': 'Second',
-                'votes': 0,
-                'selected': False
-            }, {
-                'hash': 'gggggggggggg',
-                'label': 'Third',
-                'votes': 2,
-                'selected': True
-            }, {
-                'hash': 'dddddddddddd',
-                'label': 'Fourth',
-                'votes': 1,
-                'selected': True
-            }]
+            response_json['choices'],
+            [
+                {
+                    'hash': 'aaaaaaaaaaaa',
+                    'label': 'First',
+                    'votes': 1,
+                    'selected': False,
+                },
+                {
+                    'hash': 'bbbbbbbbbbbb',
+                    'label': 'Second',
+                    'votes': 0,
+                    'selected': False,
+                },
+                {
+                    'hash': 'gggggggggggg',
+                    'label': 'Third',
+                    'votes': 2,
+                    'selected': True,
+                },
+                {
+                    'hash': 'dddddddddddd',
+                    'label': 'Fourth',
+                    'votes': 1,
+                    'selected': True,
+                },
+            ],
         )
         )
 
 
         # no votes were removed
         # no votes were removed
@@ -388,19 +439,23 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
                     True,
                     True,
                 'is_public':
                 'is_public':
                     True,
                     True,
-                'choices': [{
-                    'hash': 'aaaaaaaaaaaa',
-                    'label': '\nFirst ',
-                    'votes': 5555
-                }, {
-                    'hash': 'bbbbbbbbbbbb',
-                    'label': 'Second',
-                    'votes': 5555
-                }, {
-                    'hash': 'dsadsadsa788',
-                    'label': 'New Option',
-                    'votes': 5555
-                }]
+                'choices': [
+                    {
+                        'hash': 'aaaaaaaaaaaa',
+                        'label': '\nFirst ',
+                        'votes': 5555,
+                    },
+                    {
+                        'hash': 'bbbbbbbbbbbb',
+                        'label': 'Second',
+                        'votes': 5555,
+                    },
+                    {
+                        'hash': 'dsadsadsa788',
+                        'label': 'New Option',
+                        'votes': 5555,
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -419,22 +474,27 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         # choices were updated
         # choices were updated
         self.assertEqual(len(response_json['choices']), 3)
         self.assertEqual(len(response_json['choices']), 3)
         self.assertEqual(
         self.assertEqual(
-            response_json['choices'], [{
-                'hash': 'aaaaaaaaaaaa',
-                'label': 'First',
-                'votes': 1,
-                'selected': False
-            }, {
-                'hash': 'bbbbbbbbbbbb',
-                'label': 'Second',
-                'votes': 0,
-                'selected': False
-            }, {
-                'hash': response_json['choices'][2]['hash'],
-                'label': 'New Option',
-                'votes': 0,
-                'selected': False
-            }]
+            response_json['choices'],
+            [
+                {
+                    'hash': 'aaaaaaaaaaaa',
+                    'label': 'First',
+                    'votes': 1,
+                    'selected': False,
+                },
+                {
+                    'hash': 'bbbbbbbbbbbb',
+                    'label': 'Second',
+                    'votes': 0,
+                    'selected': False,
+                },
+                {
+                    'hash': response_json['choices'][2]['hash'],
+                    'label': 'New Option',
+                    'votes': 0,
+                    'selected': False,
+                },
+            ],
         )
         )
 
 
         # no votes were removed
         # no votes were removed
@@ -458,13 +518,17 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
                 'allowed_choices': 2,
                 'allowed_choices': 2,
                 'allow_revotes': True,
                 'allow_revotes': True,
                 'is_public': True,
                 'is_public': True,
-                'choices': [{
-                    'label': '\nRed  '
-                }, {
-                    'label': 'Green'
-                }, {
-                    'label': 'Blue'
-                }]
+                'choices': [
+                    {
+                        'label': '\nRed  ',
+                    },
+                    {
+                        'label': 'Green',
+                    },
+                    {
+                        'label': 'Blue',
+                    },
+                ],
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 20 - 10
misago/threads/tests/test_thread_pollvotes_api.py

@@ -38,8 +38,10 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         """api validates that thread id is integer"""
         """api validates that thread id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-votes',
             'misago:api:thread-poll-votes',
-            kwargs={'thread_pk': 'kjha6dsa687sa',
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': 'kjha6dsa687sa',
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.client.get(api_link)
         response = self.client.get(api_link)
@@ -49,8 +51,10 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         """api validates that thread exists"""
         """api validates that thread exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-votes',
             'misago:api:thread-poll-votes',
-            kwargs={'thread_pk': self.thread.pk + 1,
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': self.thread.pk + 1,
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
         response = self.client.get(api_link)
         response = self.client.get(api_link)
@@ -60,8 +64,10 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         """api validates that poll id is integer"""
         """api validates that poll id is integer"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-votes',
             'misago:api:thread-poll-votes',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': 'sad98as7d97sa98'}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': 'sad98as7d97sa98',
+            }
         )
         )
 
 
         response = self.client.get(api_link)
         response = self.client.get(api_link)
@@ -71,8 +77,10 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         """api validates that poll exists"""
         """api validates that poll exists"""
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-poll-votes',
             'misago:api:thread-poll-votes',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.poll.pk + 123}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.poll.pk + 123,
+            }
         )
         )
 
 
         response = self.client.get(api_link)
         response = self.client.get(api_link)
@@ -152,8 +160,10 @@ class ThreadPostVotesTests(ThreadPollApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-poll-votes',
             'misago:api:thread-poll-votes',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.poll.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.poll.pk,
+            }
         )
         )
 
 
     def delete_user_votes(self):
     def delete_user_votes(self):

+ 37 - 11
misago/threads/tests/test_thread_postdelete_api.py

@@ -17,8 +17,10 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def test_delete_anonymous(self):
     def test_delete_anonymous(self):
@@ -37,7 +39,11 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 
     def test_delete_other_user_post_no_permission(self):
     def test_delete_other_user_post_no_permission(self):
         """api valdiates if user can delete other users posts"""
         """api valdiates if user can delete other users posts"""
-        self.override_acl({'post_edit_time': 0, 'can_hide_own_posts': 2, 'can_hide_posts': 0})
+        self.override_acl({
+            'post_edit_time': 0,
+            'can_hide_own_posts': 2,
+            'can_hide_posts': 0,
+        })
 
 
         self.post.poster = None
         self.post.poster = None
         self.post.save()
         self.post.save()
@@ -117,8 +123,10 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.thread.first_post_id}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.thread.first_post_id,
+            }
         )
         )
 
 
         response = self.client.delete(api_link)
         response = self.client.delete(api_link)
@@ -126,7 +134,11 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 
     def test_delete_event(self):
     def test_delete_event(self):
         """api differs posts from events"""
         """api differs posts from events"""
-        self.override_acl({'can_hide_own_posts': 2, 'can_hide_posts': 2, 'can_hide_events': 0})
+        self.override_acl({
+            'can_hide_own_posts': 2,
+            'can_hide_posts': 2,
+            'can_hide_events': 0,
+        })
 
 
         self.post.is_event = True
         self.post.is_event = True
         self.post.save()
         self.post.save()
@@ -136,7 +148,11 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 
     def test_delete_owned_post(self):
     def test_delete_owned_post(self):
         """api deletes owned thread post"""
         """api deletes owned thread post"""
-        self.override_acl({'post_edit_time': 0, 'can_hide_own_posts': 2, 'can_hide_posts': 0})
+        self.override_acl({
+            'post_edit_time': 0,
+            'can_hide_own_posts': 2,
+            'can_hide_posts': 0,
+        })
 
 
         response = self.client.delete(self.api_link)
         response = self.client.delete(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -169,8 +185,10 @@ class EventDeleteApiTests(ThreadsApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.event.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.event.pk,
+            }
         )
         )
 
 
     def test_delete_anonymous(self):
     def test_delete_anonymous(self):
@@ -182,14 +200,22 @@ class EventDeleteApiTests(ThreadsApiTestCase):
 
 
     def test_no_permission(self):
     def test_no_permission(self):
         """api validates permission to delete event"""
         """api validates permission to delete event"""
-        self.override_acl({'can_hide_own_posts': 2, 'can_hide_posts': 2, 'can_hide_events': 0})
+        self.override_acl({
+            'can_hide_own_posts': 2,
+            'can_hide_posts': 2,
+            'can_hide_events': 0,
+        })
 
 
         response = self.client.delete(self.api_link)
         response = self.client.delete(self.api_link)
         self.assertContains(response, "You can't delete events in this category.", status_code=403)
         self.assertContains(response, "You can't delete events in this category.", status_code=403)
 
 
     def test_delete_event(self):
     def test_delete_event(self):
         """api differs posts from events"""
         """api differs posts from events"""
-        self.override_acl({'can_hide_own_posts': 0, 'can_hide_posts': 0, 'can_hide_events': 2})
+        self.override_acl({
+            'can_hide_own_posts': 0,
+            'can_hide_posts': 0,
+            'can_hide_events': 2,
+        })
 
 
         response = self.client.delete(self.api_link)
         response = self.client.delete(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 8 - 6
misago/threads/tests/test_thread_postedits_api.py

@@ -32,16 +32,18 @@ class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
                 editor_slug=self.user.slug,
                 editor_slug=self.user.slug,
                 editor_ip='127.0.0.1',
                 editor_ip='127.0.0.1',
                 edited_from="Original body",
                 edited_from="Original body",
-                edited_to="First Edit"
-            ), self.post.edits_record.create(
+                edited_to="First Edit",
+            ),
+            self.post.edits_record.create(
                 category=self.category,
                 category=self.category,
                 thread=self.thread,
                 thread=self.thread,
                 editor_name='Deleted',
                 editor_name='Deleted',
                 editor_slug='deleted',
                 editor_slug='deleted',
                 editor_ip='127.0.0.1',
                 editor_ip='127.0.0.1',
                 edited_from="First Edit",
                 edited_from="First Edit",
-                edited_to="Second Edit"
-            ), self.post.edits_record.create(
+                edited_to="Second Edit",
+            ),
+            self.post.edits_record.create(
                 category=self.category,
                 category=self.category,
                 thread=self.thread,
                 thread=self.thread,
                 editor=self.user,
                 editor=self.user,
@@ -49,8 +51,8 @@ class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
                 editor_slug=self.user.slug,
                 editor_slug=self.user.slug,
                 editor_ip='127.0.0.1',
                 editor_ip='127.0.0.1',
                 edited_from="Second Edit",
                 edited_from="Second Edit",
-                edited_to="Last Edit"
-            )
+                edited_to="Last Edit",
+            ),
         ]
         ]
 
 
         self.post.original = 'Last Edit'
         self.post.original = 'Last Edit'

+ 4 - 2
misago/threads/tests/test_thread_postlikes_api.py

@@ -14,8 +14,10 @@ class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-likes',
             'misago:api:thread-post-likes',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def test_no_permission(self):
     def test_no_permission(self):

+ 70 - 34
misago/threads/tests/test_thread_postmerge_api.py

@@ -22,7 +22,9 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.post = testutils.reply_thread(self.thread, poster=self.user)
         self.post = testutils.reply_thread(self.thread, poster=self.user)
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-merge', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-merge', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
         self.override_acl()
         self.override_acl()
@@ -39,7 +41,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             'can_reply_threads': 0,
             'can_reply_threads': 0,
             'can_edit_posts': 1,
             'can_edit_posts': 1,
             'can_approve_content': 0,
             'can_approve_content': 0,
-            'can_merge_posts': 1
+            'can_merge_posts': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -51,14 +53,22 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         """you need to authenticate to merge posts"""
         """you need to authenticate to merge posts"""
         self.logout_user()
         self.logout_user()
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
     def test_no_permission(self):
     def test_no_permission(self):
         """api validates permission to merge"""
         """api validates permission to merge"""
         self.override_acl({'can_merge_posts': 0})
         self.override_acl({'can_merge_posts': 0})
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
 
 
     def test_closed_thread(self):
     def test_closed_thread(self):
@@ -66,13 +76,21 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.thread.is_closed = True
         self.thread.is_closed = True
         self.thread.save()
         self.thread.save()
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
 
 
         # allow closing threads
         # allow closing threads
         self.override_acl({'can_close_threads': 1})
         self.override_acl({'can_close_threads': 1})
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
             response, "You have to select at least two posts to merge.", status_code=400
         )
         )
@@ -82,20 +100,32 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
         self.assertContains(response, "You can't merge posts in this thread.", status_code=403)
 
 
         # allow closing threads
         # allow closing threads
         self.override_acl({'can_close_threads': 1})
         self.override_acl({'can_close_threads': 1})
 
 
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
             response, "You have to select at least two posts to merge.", status_code=400
         )
         )
 
 
     def test_empty_data(self):
     def test_empty_data(self):
         """api handles empty data"""
         """api handles empty data"""
-        response = self.client.post(self.api_link, json.dumps({}), content_type="application/json")
+        response = self.client.post(
+            self.api_link,
+            json.dumps({}),
+            content_type="application/json",
+        )
         self.assertContains(
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
             response, "You have to select at least two posts to merge.", status_code=400
         )
         )
@@ -103,9 +133,11 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def test_no_posts_ids(self):
     def test_no_posts_ids(self):
         """api rejects no posts ids"""
         """api rejects no posts ids"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
+            self.api_link,
+            json.dumps({
                 'posts': []
                 'posts': []
-            }), content_type="application/json"
+            }),
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
             response, "You have to select at least two posts to merge.", status_code=400
@@ -114,9 +146,11 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def test_invalid_posts_data(self):
     def test_invalid_posts_data(self):
         """api handles invalid data"""
         """api handles invalid data"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
+            self.api_link,
+            json.dumps({
                 'posts': 'string'
                 'posts': 'string'
-            }), content_type="application/json"
+            }),
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -129,7 +163,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [1, 2, 'string']
                 'posts': [1, 2, 'string']
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -138,9 +172,11 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def test_one_post_id(self):
     def test_one_post_id(self):
         """api rejects one post id"""
         """api rejects one post id"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
+            self.api_link,
+            json.dumps({
                 'posts': [1]
                 'posts': [1]
-            }), content_type="application/json"
+            }),
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You have to select at least two posts to merge.", status_code=400
             response, "You have to select at least two posts to merge.", status_code=400
@@ -153,7 +189,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': list(range(MERGE_LIMIT + 1))
                 'posts': list(range(MERGE_LIMIT + 1))
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "No more than {} posts can be merged".format(MERGE_LIMIT), status_code=400
             response, "No more than {} posts can be merged".format(MERGE_LIMIT), status_code=400
@@ -168,7 +204,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [self.post.pk, event.pk]
                 'posts': [self.post.pk, event.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(response, "Events can't be merged.", status_code=400)
         self.assertContains(response, "Events can't be merged.", status_code=400)
 
 
@@ -179,7 +215,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [self.post.pk, self.post.pk * 1000]
                 'posts': [self.post.pk, self.post.pk * 1000]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to merge could not be found.", status_code=400
             response, "One or more posts to merge could not be found.", status_code=400
@@ -195,7 +231,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [self.post.pk, other_post.pk]
                 'posts': [self.post.pk, other_post.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to merge could not be found.", status_code=400
             response, "One or more posts to merge could not be found.", status_code=400
@@ -210,7 +246,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [self.post.pk, other_post.pk]
                 'posts': [self.post.pk, other_post.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "Posts made by different users can't be merged.", status_code=400
             response, "Posts made by different users can't be merged.", status_code=400
@@ -225,7 +261,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [other_post.pk, self.post.pk]
                 'posts': [other_post.pk, self.post.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "Posts made by different users can't be merged.", status_code=400
             response, "Posts made by different users can't be merged.", status_code=400
@@ -238,10 +274,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [
                 'posts': [
                     testutils.reply_thread(self.thread, poster="Bob").pk,
                     testutils.reply_thread(self.thread, poster="Bob").pk,
-                    testutils.reply_thread(self.thread, poster="Miku").pk
+                    testutils.reply_thread(self.thread, poster="Miku").pk,
                 ]
                 ]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "Posts made by different users can't be merged.", status_code=400
             response, "Posts made by different users can't be merged.", status_code=400
@@ -256,10 +292,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [
                 'posts': [
                     testutils.reply_thread(self.thread, poster="Bob", is_hidden=True).pk,
                     testutils.reply_thread(self.thread, poster="Bob", is_hidden=True).pk,
-                    testutils.reply_thread(self.thread, poster="Bob", is_hidden=False).pk
+                    testutils.reply_thread(self.thread, poster="Bob", is_hidden=False).pk,
                 ]
                 ]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "Posts with different visibility can't be merged.", status_code=400
             response, "Posts with different visibility can't be merged.", status_code=400
@@ -274,10 +310,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [
                 'posts': [
                     testutils.reply_thread(self.thread, poster="Bob", is_unapproved=True).pk,
                     testutils.reply_thread(self.thread, poster="Bob", is_unapproved=True).pk,
-                    testutils.reply_thread(self.thread, poster="Bob", is_unapproved=False).pk
+                    testutils.reply_thread(self.thread, poster="Bob", is_unapproved=False).pk,
                 ]
                 ]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "Posts with different visibility can't be merged.", status_code=400
             response, "Posts with different visibility can't be merged.", status_code=400
@@ -295,7 +331,7 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [post_a.pk, post_b.pk]
                 'posts': [post_a.pk, post_b.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -317,10 +353,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [
                 'posts': [
                     testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk,
                     testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk,
-                    testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk
+                    testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk,
                 ]
                 ]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -333,10 +369,10 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [
                 'posts': [
                     testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk,
                     testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk,
-                    testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk
+                    testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk,
                 ]
                 ]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -355,6 +391,6 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': [self.thread.first_post.pk, post_visible.pk]
                 'posts': [self.thread.first_post.pk, post_visible.pk]
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 54 - 30
misago/threads/tests/test_thread_postmove_api.py

@@ -21,14 +21,18 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
         self.thread = testutils.post_thread(category=self.category)
         self.thread = testutils.post_thread(category=self.category)
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-move', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-move', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
         Category(
         Category(
             name='Category B',
             name='Category B',
             slug='category-b',
             slug='category-b',
         ).insert_at(
         ).insert_at(
-            self.category, position='last-child', save=True
+            self.category,
+            position='last-child',
+            save=True,
         )
         )
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
 
 
@@ -47,7 +51,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             'can_reply_threads': 1,
             'can_reply_threads': 1,
             'can_edit_posts': 1,
             'can_edit_posts': 1,
             'can_approve_content': 0,
             'can_approve_content': 0,
-            'can_move_posts': 1
+            'can_move_posts': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -64,7 +68,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             'can_reply_threads': 0,
             'can_reply_threads': 0,
             'can_edit_posts': 1,
             'can_edit_posts': 1,
             'can_approve_content': 0,
             'can_approve_content': 0,
-            'can_move_posts': 1
+            'can_move_posts': 1,
         })
         })
 
 
         if acl:
         if acl:
@@ -105,12 +109,18 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_invalid_url(self):
     def test_invalid_url(self):
         """api validates thread url"""
         """api validates thread url"""
-        response = self.client.post(self.api_link, {'thread_url': self.user.get_absolute_url()})
+        response = self.client.post(self.api_link, {
+            'thread_url': self.user.get_absolute_url(),
+        })
         self.assertContains(response, "This is not a valid thread link.", status_code=400)
         self.assertContains(response, "This is not a valid thread link.", status_code=400)
 
 
     def test_current_thread_url(self):
     def test_current_thread_url(self):
         """api validates if thread url given is to current thread"""
         """api validates if thread url given is to current thread"""
-        response = self.client.post(self.api_link, {'thread_url': self.thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': self.thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "Thread to move posts to is same as current one.", status_code=400
             response, "Thread to move posts to is same as current one.", status_code=400
         )
         )
@@ -123,7 +133,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
         other_thread_url = other_thread.get_absolute_url()
         other_thread_url = other_thread.get_absolute_url()
         other_thread.delete()
         other_thread.delete()
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread_url})
+        response = self.client.post(self.api_link, {
+            'thread_url': other_thread_url,
+        })
         self.assertContains(
         self.assertContains(
             response, "The thread you have entered link to doesn't exist", status_code=400
             response, "The thread you have entered link to doesn't exist", status_code=400
         )
         )
@@ -134,7 +146,11 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "The thread you have entered link to doesn't exist", status_code=400
             response, "The thread you have entered link to doesn't exist", status_code=400
         )
         )
@@ -145,7 +161,11 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "You can't move posts to threads you can't reply.", status_code=400
             response, "You can't move posts to threads you can't reply.", status_code=400
         )
         )
@@ -154,7 +174,11 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
         """api handles empty data"""
         """api handles empty data"""
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
-        response = self.client.post(self.api_link, {'thread_url': other_thread.get_absolute_url()})
+        response = self.client.post(
+            self.api_link, {
+                'thread_url': other_thread.get_absolute_url(),
+            }
+        )
         self.assertContains(
         self.assertContains(
             response, "You have to specify at least one post to move.", status_code=400
             response, "You have to specify at least one post to move.", status_code=400
         )
         )
@@ -167,9 +191,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': []
+                'posts': [],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You have to specify at least one post to move.", status_code=400
             response, "You have to specify at least one post to move.", status_code=400
@@ -183,9 +207,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': 'string'
+                'posts': 'string',
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -199,9 +223,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [1, 2, 'string']
+                'posts': [1, 2, 'string'],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -215,9 +239,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': list(range(MOVE_LIMIT + 1))
+                'posts': list(range(MOVE_LIMIT + 1)),
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "No more than {} posts can be moved".format(MOVE_LIMIT), status_code=400
             response, "No more than {} posts can be moved".format(MOVE_LIMIT), status_code=400
@@ -231,9 +255,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [testutils.reply_thread(self.thread, is_unapproved=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_unapproved=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to move could not be found.", status_code=400
             response, "One or more posts to move could not be found.", status_code=400
@@ -247,9 +271,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [testutils.reply_thread(other_thread, is_hidden=True).pk]
+                'posts': [testutils.reply_thread(other_thread, is_hidden=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to move could not be found.", status_code=400
             response, "One or more posts to move could not be found.", status_code=400
@@ -263,9 +287,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [testutils.reply_thread(self.thread, is_event=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_event=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(response, "Events can't be moved.", status_code=400)
         self.assertContains(response, "Events can't be moved.", status_code=400)
 
 
@@ -277,9 +301,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [self.thread.first_post_id]
+                'posts': [self.thread.first_post_id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(response, "You can't move thread's first post.", status_code=400)
         self.assertContains(response, "You can't move thread's first post.", status_code=400)
 
 
@@ -291,9 +315,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': [testutils.reply_thread(self.thread, is_hidden=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_hidden=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You can't move posts the content you can't see.", status_code=400
             response, "You can't move posts the content you can't see.", status_code=400
@@ -317,9 +341,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
                 'thread_url': other_thread.get_absolute_url(),
                 'thread_url': other_thread.get_absolute_url(),
-                'posts': posts
+                'posts': posts,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 

+ 361 - 228
misago/threads/tests/test_thread_postpatch_api.py

@@ -24,8 +24,10 @@ class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def patch(self, api_link, ops):
     def patch(self, api_link, ops):
@@ -41,7 +43,7 @@ class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 0,
             'can_start_threads': 0,
             'can_reply_threads': 0,
             'can_reply_threads': 0,
-            'can_edit_posts': 1
+            'can_edit_posts': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -53,7 +55,13 @@ class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
 class PostAddAclApiTests(ThreadPostPatchApiTestCase):
 class PostAddAclApiTests(ThreadPostPatchApiTestCase):
     def test_add_acl_true(self):
     def test_add_acl_true(self):
         """api adds current event's acl to response"""
         """api adds current event's acl to response"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -61,13 +69,25 @@ class PostAddAclApiTests(ThreadPostPatchApiTestCase):
 
 
     def test_add_acl_false(self):
     def test_add_acl_false(self):
         """if value is false, api won't add acl to the response, but will set empty key"""
         """if value is false, api won't add acl to the response, but will set empty key"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': False}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': False,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertIsNone(response_json['acl'])
         self.assertIsNone(response_json['acl'])
 
 
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
 
 
@@ -77,11 +97,13 @@ class PostProtectApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_protect_posts': 1})
         self.override_acl({'can_protect_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-protected',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-protected',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -96,11 +118,13 @@ class PostProtectApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_protect_posts': 1})
         self.override_acl({'can_protect_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-protected',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-protected',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -112,11 +136,13 @@ class PostProtectApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_protect_posts': 0})
         self.override_acl({'can_protect_posts': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-protected',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-protected',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -134,11 +160,13 @@ class PostProtectApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_protect_posts': 0})
         self.override_acl({'can_protect_posts': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-protected',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-protected',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -153,11 +181,13 @@ class PostProtectApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_edit_posts': 0, 'can_protect_posts': 1})
         self.override_acl({'can_edit_posts': 0, 'can_protect_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-protected',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-protected',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -177,11 +207,13 @@ class PostApproveApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -193,11 +225,13 @@ class PostApproveApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -212,11 +246,13 @@ class PostApproveApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_approve_content': 0})
         self.override_acl({'can_approve_content': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -237,11 +273,13 @@ class PostApproveApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -260,11 +298,13 @@ class PostApproveApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_approve_content': 1})
         self.override_acl({'can_approve_content': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-unapproved',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-unapproved',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -283,11 +323,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 1})
         self.override_acl({'can_hide_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -305,11 +347,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 1})
         self.override_acl({'can_hide_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -321,11 +365,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -343,11 +389,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -359,11 +407,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 0})
         self.override_acl({'can_hide_posts': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -384,11 +434,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 0})
         self.override_acl({'can_hide_posts': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -406,11 +458,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_protect_posts': 0, 'can_hide_own_posts': 1})
         self.override_acl({'can_protect_posts': 0, 'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -431,11 +485,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.post.save()
         self.post.save()
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -455,11 +511,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -480,11 +538,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -504,11 +564,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
         self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -529,11 +591,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
         self.override_acl({'post_edit_time': 1, 'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -553,11 +617,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -580,11 +646,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -604,11 +672,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -631,11 +701,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_own_posts': 1})
         self.override_acl({'can_hide_own_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -655,11 +727,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 1})
         self.override_acl({'can_hide_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -674,11 +748,13 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_hide_posts': 1})
         self.override_acl({'can_hide_posts': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -692,11 +768,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_see_posts_likes': 0})
         self.override_acl({'can_see_posts_likes': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
 
 
@@ -705,22 +783,26 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         self.override_acl({'can_like_posts': False})
         self.override_acl({'can_like_posts': False})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
 
 
     def test_like_post(self):
     def test_like_post(self):
         """api adds user like to post"""
         """api adds user like to post"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -728,10 +810,12 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         self.assertEqual(response_json['likes'], 1)
         self.assertEqual(response_json['likes'], 1)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(
         self.assertEqual(
-            response_json['last_likes'], [{
-                'id': self.user.id,
-                'username': self.user.username
-            }]
+            response_json['last_likes'], [
+                {
+                    'id': self.user.id,
+                    'username': self.user.username,
+                },
+            ]
         )
         )
 
 
         post = Post.objects.get(pk=self.post.pk)
         post = Post.objects.get(pk=self.post.pk)
@@ -746,11 +830,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         testutils.like_post(self.post, username='Miku')
         testutils.like_post(self.post, username='Miku')
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -758,19 +844,24 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         self.assertEqual(response_json['likes'], 5)
         self.assertEqual(response_json['likes'], 5)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(
         self.assertEqual(
-            response_json['last_likes'], [{
-                'id': self.user.id,
-                'username': self.user.username
-            }, {
-                'id': None,
-                'username': 'Miku'
-            }, {
-                'id': None,
-                'username': 'Bob'
-            }, {
-                'id': None,
-                'username': 'Mugi'
-            }]
+            response_json['last_likes'], [
+                {
+                    'id': self.user.id,
+                    'username': self.user.username
+                },
+                {
+                    'id': None,
+                    'username': 'Miku',
+                },
+                {
+                    'id': None,
+                    'username': 'Bob',
+                },
+                {
+                    'id': None,
+                    'username': 'Mugi',
+                },
+            ]
         )
         )
 
 
         post = Post.objects.get(pk=self.post.pk)
         post = Post.objects.get(pk=self.post.pk)
@@ -782,11 +873,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         testutils.like_post(self.post, self.user)
         testutils.like_post(self.post, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -804,11 +897,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         testutils.like_post(self.post, self.user)
         testutils.like_post(self.post, self.user)
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -816,10 +911,12 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         self.assertEqual(response_json['likes'], 1)
         self.assertEqual(response_json['likes'], 1)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(response_json['is_liked'], True)
         self.assertEqual(
         self.assertEqual(
-            response_json['last_likes'], [{
-                'id': self.user.id,
-                'username': self.user.username
-            }]
+            response_json['last_likes'], [
+                {
+                    'id': self.user.id,
+                    'username': self.user.username,
+                },
+            ]
         )
         )
 
 
         post = Post.objects.get(pk=self.post.pk)
         post = Post.objects.get(pk=self.post.pk)
@@ -829,11 +926,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
     def test_unlike_post_no_change(self):
     def test_unlike_post_no_change(self):
         """api does no state change if we are unlinking unliked post"""
         """api does no state change if we are unlinking unliked post"""
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-liked',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-liked',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -851,8 +950,10 @@ class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-detail',
             'misago:api:thread-post-detail',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.event.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.event.pk,
+            }
         )
         )
 
 
     def refresh_event(self):
     def refresh_event(self):
@@ -864,14 +965,26 @@ class EventAnonPatchApiTests(ThreadEventPatchApiTestCase):
         """anonymous users can't change event state"""
         """anonymous users can't change event state"""
         self.logout_user()
         self.logout_user()
 
 
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
 
 
 class EventAddAclApiTests(ThreadEventPatchApiTestCase):
 class EventAddAclApiTests(ThreadEventPatchApiTestCase):
     def test_add_acl_true(self):
     def test_add_acl_true(self):
         """api adds current event's acl to response"""
         """api adds current event's acl to response"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
@@ -879,13 +992,25 @@ class EventAddAclApiTests(ThreadEventPatchApiTestCase):
 
 
     def test_add_acl_false(self):
     def test_add_acl_false(self):
         """if value is false, api won't add acl to the response, but will set empty key"""
         """if value is false, api won't add acl to the response, but will set empty key"""
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': False}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': False,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertIsNone(response_json['acl'])
         self.assertIsNone(response_json['acl'])
 
 
-        response = self.patch(self.api_link, [{'op': 'add', 'path': 'acl', 'value': True}])
+        response = self.patch(self.api_link, [
+            {
+                'op': 'add',
+                'path': 'acl',
+                'value': True,
+            },
+        ])
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
 
 
@@ -895,11 +1020,13 @@ class EventHideApiTests(ThreadEventPatchApiTestCase):
         self.override_acl({'can_hide_events': 1})
         self.override_acl({'can_hide_events': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -917,11 +1044,13 @@ class EventHideApiTests(ThreadEventPatchApiTestCase):
         self.override_acl({'can_hide_events': 1})
         self.override_acl({'can_hide_events': 1})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -933,11 +1062,13 @@ class EventHideApiTests(ThreadEventPatchApiTestCase):
         self.override_acl({'can_hide_events': 0})
         self.override_acl({'can_hide_events': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': True
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': True,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -960,10 +1091,12 @@ class EventHideApiTests(ThreadEventPatchApiTestCase):
         self.override_acl({'can_hide_events': 0})
         self.override_acl({'can_hide_events': 0})
 
 
         response = self.patch(
         response = self.patch(
-            self.api_link, [{
-                'op': 'replace',
-                'path': 'is-hidden',
-                'value': False
-            }]
+            self.api_link, [
+                {
+                    'op': 'replace',
+                    'path': 'is-hidden',
+                    'value': False,
+                },
+            ]
         )
         )
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)

+ 10 - 4
misago/threads/tests/test_thread_postread_api.py

@@ -10,12 +10,18 @@ class PostReadApiTests(ThreadsApiTestCase):
     def setUp(self):
     def setUp(self):
         super(PostReadApiTests, self).setUp()
         super(PostReadApiTests, self).setUp()
 
 
-        self.post = testutils.reply_thread(self.thread, poster=self.user, posted_on=timezone.now())
+        self.post = testutils.reply_thread(
+            self.thread,
+            poster=self.user,
+            posted_on=timezone.now(),
+        )
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-read',
             'misago:api:thread-post-read',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def test_read_anonymous(self):
     def test_read_anonymous(self):
@@ -43,7 +49,7 @@ class PostReadApiTests(ThreadsApiTestCase):
             user=self.user,
             user=self.user,
             thread=self.thread,
             thread=self.thread,
             category=self.thread.category,
             category=self.thread.category,
-            last_read_on=self.thread.post_set.order_by('id').first().posted_on
+            last_read_on=self.thread.post_set.order_by('id').first().posted_on,
         )
         )
 
 
         response = self.client.post(self.api_link)
         response = self.client.post(self.api_link)

+ 94 - 67
misago/threads/tests/test_thread_postsplit_api.py

@@ -24,14 +24,18 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         ]
         ]
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-split', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-split', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
         Category(
         Category(
             name='Category B',
             name='Category B',
             slug='category-b',
             slug='category-b',
         ).insert_at(
         ).insert_at(
-            self.category, position='last-child', save=True
+            self.category,
+            position='last-child',
+            save=True,
         )
         )
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
 
 
@@ -50,7 +54,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             'can_reply_threads': 1,
             'can_reply_threads': 1,
             'can_edit_posts': 1,
             'can_edit_posts': 1,
             'can_approve_content': 0,
             'can_approve_content': 0,
-            'can_move_posts': 1
+            'can_move_posts': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -67,7 +71,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             'can_reply_threads': 0,
             'can_reply_threads': 0,
             'can_edit_posts': 1,
             'can_edit_posts': 1,
             'can_approve_content': 0,
             'can_approve_content': 0,
-            'can_move_posts': 1
+            'can_move_posts': 1,
         })
         })
 
 
         if acl:
         if acl:
@@ -111,9 +115,11 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     def test_no_posts_ids(self):
     def test_no_posts_ids(self):
         """api rejects no posts ids"""
         """api rejects no posts ids"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'posts': []
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'posts': [],
+            }),
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You have to specify at least one post to split.", status_code=400
             response, "You have to specify at least one post to split.", status_code=400
@@ -122,9 +128,11 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     def test_invalid_posts_data(self):
     def test_invalid_posts_data(self):
         """api handles invalid data"""
         """api handles invalid data"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'posts': 'string'
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'posts': 'string',
+            }),
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -135,9 +143,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [1, 2, 'string']
+                'posts': [1, 2, 'string'],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400
             response, "One or more post ids received were invalid.", status_code=400
@@ -148,9 +156,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': list(range(SPLIT_LIMIT + 1))
+                'posts': list(range(SPLIT_LIMIT + 1)),
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "No more than {} posts can be split".format(SPLIT_LIMIT), status_code=400
             response, "No more than {} posts can be split".format(SPLIT_LIMIT), status_code=400
@@ -161,9 +169,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [testutils.reply_thread(self.thread, is_unapproved=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_unapproved=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to split could not be found.", status_code=400
             response, "One or more posts to split could not be found.", status_code=400
@@ -174,9 +182,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [testutils.reply_thread(self.thread, is_event=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_event=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(response, "Events can't be split.", status_code=400)
         self.assertContains(response, "Events can't be split.", status_code=400)
 
 
@@ -185,9 +193,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [self.thread.first_post_id]
+                'posts': [self.thread.first_post_id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(response, "You can't split thread's first post.", status_code=400)
         self.assertContains(response, "You can't split thread's first post.", status_code=400)
 
 
@@ -196,9 +204,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [testutils.reply_thread(self.thread, is_hidden=True).pk]
+                'posts': [testutils.reply_thread(self.thread, is_hidden=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "You can't split posts the content you can't see.", status_code=400
             response, "You can't split posts the content you can't see.", status_code=400
@@ -211,9 +219,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'posts': [testutils.reply_thread(other_thread, is_hidden=True).pk]
+                'posts': [testutils.reply_thread(other_thread, is_hidden=True).pk],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertContains(
         self.assertContains(
             response, "One or more posts to split could not be found.", status_code=400
             response, "One or more posts to split could not be found.", status_code=400
@@ -222,9 +230,11 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     def test_split_empty_new_thread_data(self):
     def test_split_empty_new_thread_data(self):
         """api handles empty form data"""
         """api handles empty form data"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'posts': self.posts
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'posts': self.posts,
+            }),
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -243,16 +253,17 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': self.posts,
                 'posts': self.posts,
                 'title': '$$$',
                 'title': '$$$',
-                'category': self.category.id
+                'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_split_invalid_category(self):
     def test_split_invalid_category(self):
@@ -264,14 +275,18 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': self.posts,
                 'posts': self.posts,
                 'title': 'Valid thread title',
                 'title': 'Valid thread title',
-                'category': self.category_b.id
+                'category': self.category_b.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
-        self.assertEqual(response_json, {'category': ["Requested category could not be found."]})
+        self.assertEqual(
+            response_json, {
+                'category': ["Requested category could not be found."],
+            }
+        )
 
 
     def test_split_unallowed_start_thread(self):
     def test_split_unallowed_start_thread(self):
         """api rejects split because category isn't allowing starting threads"""
         """api rejects split because category isn't allowing starting threads"""
@@ -282,15 +297,17 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': self.posts,
                 'posts': self.posts,
                 'title': 'Valid thread title',
                 'title': 'Valid thread title',
-                'category': self.category.id
+                'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'category': ["You can't create new threads in selected category."]}
+            response_json, {
+                'category': ["You can't create new threads in selected category."],
+            }
         )
         )
 
 
     def test_split_invalid_weight(self):
     def test_split_invalid_weight(self):
@@ -303,13 +320,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 4,
                 'weight': 4,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'weight': ["Ensure this value is less than or equal to 2."]}
+            response_json, {
+                'weight': ["Ensure this value is less than or equal to 2."],
+            }
         )
         )
 
 
     def test_split_unallowed_global_weight(self):
     def test_split_unallowed_global_weight(self):
@@ -322,14 +341,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 2,
                 'weight': 2,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'weight': ["You don't have permission to pin threads globally in this category."]}
+            response_json, {
+                'weight': ["You don't have permission to pin threads globally in this category."],
+            }
         )
         )
 
 
     def test_split_unallowed_local_weight(self):
     def test_split_unallowed_local_weight(self):
@@ -348,8 +368,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'weight': ["You don't have permission to pin threads in this category."]}
+            response_json, {
+                'weight': ["You don't have permission to pin threads in this category."],
+            }
         )
         )
 
 
     def test_split_allowed_local_weight(self):
     def test_split_allowed_local_weight(self):
@@ -364,14 +385,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 1,
                 'weight': 1,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_split_allowed_global_weight(self):
     def test_split_allowed_global_weight(self):
@@ -386,14 +408,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 2,
                 'weight': 2,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_split_unallowed_close(self):
     def test_split_unallowed_close(self):
@@ -406,14 +429,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'is_closed': True,
                 'is_closed': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'is_closed': ["You don't have permission to close threads in this category."]}
+            response_json, {
+                'is_closed': ["You don't have permission to close threads in this category."],
+            }
         )
         )
 
 
     def test_split_with_close(self):
     def test_split_with_close(self):
@@ -429,14 +453,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'weight': 0,
                 'weight': 0,
                 'is_closed': True,
                 'is_closed': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_split_unallowed_hidden(self):
     def test_split_unallowed_hidden(self):
@@ -449,14 +474,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'is_hidden': True,
                 'is_hidden': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'is_hidden': ["You don't have permission to hide threads in this category."]}
+            response_json, {
+                'is_hidden': ["You don't have permission to hide threads in this category."],
+            }
         )
         )
 
 
     def test_split_with_hide(self):
     def test_split_with_hide(self):
@@ -472,14 +498,15 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'weight': 0,
                 'weight': 0,
                 'is_hidden': True,
                 'is_hidden': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_split(self):
     def test_split(self):
@@ -492,9 +519,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             json.dumps({
             json.dumps({
                 'posts': self.posts,
                 'posts': self.posts,
                 'title': 'Split thread.',
                 'title': 'Split thread.',
-                'category': self.category.id
+                'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -518,7 +545,7 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
             'can_start_threads': 2,
             'can_start_threads': 2,
             'can_close_threads': True,
             'can_close_threads': True,
             'can_hide_threads': True,
             'can_hide_threads': True,
-            'can_pin_threads': 2
+            'can_pin_threads': 2,
         })
         })
 
 
         response = self.client.post(
         response = self.client.post(
@@ -529,9 +556,9 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
                 'category': self.category_b.id,
                 'category': self.category_b.id,
                 'weight': 2,
                 'weight': 2,
                 'is_closed': 1,
                 'is_closed': 1,
-                'is_hidden': 1
+                'is_hidden': 1,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 

+ 18 - 7
misago/threads/tests/test_thread_reply_api.py

@@ -18,7 +18,9 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.thread = testutils.post_thread(category=self.category)
         self.thread = testutils.post_thread(category=self.category)
 
 
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def override_acl(self, extra_acl=None):
     def override_acl(self, extra_acl=None):
@@ -27,7 +29,7 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
             'can_see': 1,
             'can_see': 1,
             'can_browse': 1,
             'can_browse': 1,
             'can_start_threads': 0,
             'can_start_threads': 0,
-            'can_reply_threads': 1
+            'can_reply_threads': 1,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -109,7 +111,9 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
 
 
         response = self.client.post(self.api_link, data={})
         response = self.client.post(self.api_link, data={})
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'post': ["You have to enter a message."]})
+        self.assertEqual(response.json(), {
+            'post': ["You have to enter a message."],
+        })
 
 
     def test_post_is_validated(self):
     def test_post_is_validated(self):
         """post is validated"""
         """post is validated"""
@@ -123,14 +127,19 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'post': ["Posted message should be at least 5 characters long (it has 1)."]}
+            response.json(), {
+                'post': ["Posted message should be at least 5 characters long (it has 1)."],
+            }
         )
         )
 
 
     def test_can_reply_thread(self):
     def test_can_reply_thread(self):
         """endpoint creates new reply"""
         """endpoint creates new reply"""
         self.override_acl()
         self.override_acl()
-        response = self.client.post(self.api_link, data={'post': "This is test response!"})
+        response = self.client.post(
+            self.api_link, data={
+                'post': "This is test response!",
+            }
+        )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
         thread = Thread.objects.get(pk=self.thread.pk)
         thread = Thread.objects.get(pk=self.thread.pk)
@@ -169,6 +178,8 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.override_acl()
         self.override_acl()
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, data={'post': "Chrzążczyżewoszyce, powiat Łękółody."}
+            self.api_link, data={
+                'post': "Chrzążczyżewoszyce, powiat Łękółody.",
+            }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 31 - 20
misago/threads/tests/test_thread_start_api.py

@@ -24,7 +24,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
             'can_pin_threads': 0,
             'can_pin_threads': 0,
             'can_close_threads': 0,
             'can_close_threads': 0,
             'can_hide_threads': 0,
             'can_hide_threads': 0,
-            'can_hide_own_threads': 0
+            'can_hide_own_threads': 0,
         })
         })
 
 
         if extra_acl:
         if extra_acl:
@@ -50,7 +50,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
         """has no permission to see selected category"""
         """has no permission to see selected category"""
         self.override_acl({'can_see': 0})
         self.override_acl({'can_see': 0})
 
 
-        response = self.client.post(self.api_link, {'category': self.category.pk})
+        response = self.client.post(self.api_link, {
+            'category': self.category.pk,
+        })
 
 
         self.assertContains(response, "Selected category is invalid.", status_code=400)
         self.assertContains(response, "Selected category is invalid.", status_code=400)
 
 
@@ -58,7 +60,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
         """has no permission to browse selected category"""
         """has no permission to browse selected category"""
         self.override_acl({'can_browse': 0})
         self.override_acl({'can_browse': 0})
 
 
-        response = self.client.post(self.api_link, {'category': self.category.pk})
+        response = self.client.post(self.api_link, {
+            'category': self.category.pk,
+        })
 
 
         self.assertContains(response, "Selected category is invalid.", status_code=400)
         self.assertContains(response, "Selected category is invalid.", status_code=400)
 
 
@@ -66,7 +70,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
         """permission to start thread in category is validated"""
         """permission to start thread in category is validated"""
         self.override_acl({'can_start_threads': 0})
         self.override_acl({'can_start_threads': 0})
 
 
-        response = self.client.post(self.api_link, {'category': self.category.pk})
+        response = self.client.post(self.api_link, {
+            'category': self.category.pk,
+        })
 
 
         self.assertContains(
         self.assertContains(
             response, "You don't have permission to start new threads", status_code=400
             response, "You don't have permission to start new threads", status_code=400
@@ -79,7 +85,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         self.override_acl({'can_close_threads': 0})
         self.override_acl({'can_close_threads': 0})
 
 
-        response = self.client.post(self.api_link, {'category': self.category.pk})
+        response = self.client.post(self.api_link, {
+            'category': self.category.pk,
+        })
 
 
         self.assertContains(response, "This category is closed.", status_code=400)
         self.assertContains(response, "This category is closed.", status_code=400)
 
 
@@ -104,7 +112,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
             response.json(), {
             response.json(), {
                 'category': ["You have to select category to post thread in."],
                 'category': ["You have to select category to post thread in."],
                 'title': ["You have to enter thread title."],
                 'title': ["You have to enter thread title."],
-                'post': ["You have to enter a message."]
+                'post': ["You have to enter a message."],
             }
             }
         )
         )
 
 
@@ -123,7 +131,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(), {'title': ["Thread title should contain alpha-numeric characters."]}
+            response.json(), {
+                'title': ["Thread title should contain alpha-numeric characters."],
+            }
         )
         )
 
 
     def test_post_is_validated(self):
     def test_post_is_validated(self):
@@ -141,8 +151,9 @@ class StartThreadTests(AuthenticatedUserTestCase):
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'post': ["Posted message should be at least 5 characters long (it has 1)."]}
+            response.json(), {
+                'post': ["Posted message should be at least 5 characters long (it has 1)."],
+            }
         )
         )
 
 
     def test_can_start_thread(self):
     def test_can_start_thread(self):
@@ -153,7 +164,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
             data={
             data={
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
-                'post': "Lorem ipsum dolor met!"
+                'post': "Lorem ipsum dolor met!",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -210,7 +221,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'close': True
+                'close': True,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -228,7 +239,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'close': True
+                'close': True,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -246,7 +257,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'pin': 0
+                'pin': 0,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -264,7 +275,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'pin': 1
+                'pin': 1,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -282,7 +293,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'pin': 2
+                'pin': 2,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -300,7 +311,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'pin': 2
+                'pin': 2,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -318,7 +329,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'pin': 1
+                'pin': 1,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -336,7 +347,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'hide': 1
+                'hide': 1,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -357,7 +368,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Hello, I am test thread!",
                 'title': "Hello, I am test thread!",
                 'post': "Lorem ipsum dolor met!",
                 'post': "Lorem ipsum dolor met!",
-                'hide': 1
+                'hide': 1,
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -374,7 +385,7 @@ class StartThreadTests(AuthenticatedUserTestCase):
             data={
             data={
                 'category': self.category.pk,
                 'category': self.category.pk,
                 'title': "Brzęczyżczykiewicz",
                 'title': "Brzęczyżczykiewicz",
-                'post': "Chrzążczyżewoszyce, powiat Łękółody."
+                'post': "Chrzążczyżewoszyce, powiat Łękółody.",
             }
             }
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 2 - 2
misago/threads/tests/test_threadparticipant_model.py

@@ -21,7 +21,7 @@ class ThreadParticipantTests(TestCase):
             starter_slug='tester',
             starter_slug='tester',
             last_post_on=datetime,
             last_post_on=datetime,
             last_poster_name='Tester',
             last_poster_name='Tester',
-            last_poster_slug='tester'
+            last_poster_slug='tester',
         )
         )
 
 
         self.thread.set_title("Test thread")
         self.thread.set_title("Test thread")
@@ -36,7 +36,7 @@ class ThreadParticipantTests(TestCase):
             parsed="<p>Hello! I am test message!</p>",
             parsed="<p>Hello! I am test message!</p>",
             checksum="nope",
             checksum="nope",
             posted_on=datetime,
             posted_on=datetime,
-            updated_on=datetime
+            updated_on=datetime,
         )
         )
 
 
         self.thread.first_post = post
         self.thread.first_post = post

+ 10 - 5
misago/threads/tests/test_threads_api.py

@@ -33,7 +33,7 @@ class ThreadsApiTestCase(AuthenticatedUserTestCase):
             'can_edit_posts': 0,
             'can_edit_posts': 0,
             'can_hide_posts': 0,
             'can_hide_posts': 0,
             'can_hide_own_posts': 0,
             'can_hide_own_posts': 0,
-            'can_merge_threads': 0
+            'can_merge_threads': 0,
         })
         })
 
 
         if acl:
         if acl:
@@ -54,8 +54,8 @@ class ThreadsApiTestCase(AuthenticatedUserTestCase):
                 'visible_categories': visible_categories,
                 'visible_categories': visible_categories,
                 'browseable_categories': browseable_categories,
                 'browseable_categories': browseable_categories,
                 'categories': {
                 'categories': {
-                    self.category.pk: final_acl
-                }
+                    self.category.pk: final_acl,
+                },
             }
             }
         )
         )
 
 
@@ -129,7 +129,9 @@ class ThreadRetrieveApiTests(ThreadsApiTestCase):
         self.override_acl({'can_hide_posts': 0})
         self.override_acl({'can_hide_posts': 0})
 
 
         hidden_post = testutils.reply_thread(
         hidden_post = testutils.reply_thread(
-            self.thread, is_hidden=True, message="I'am hidden test message!"
+            self.thread,
+            is_hidden=True,
+            message="I'am hidden test message!",
         )
         )
 
 
         response = self.client.get(self.tested_links[1])
         response = self.client.get(self.tested_links[1])
@@ -146,7 +148,10 @@ class ThreadRetrieveApiTests(ThreadsApiTestCase):
         self.override_acl({'can_approve_content': 0})
         self.override_acl({'can_approve_content': 0})
 
 
         # unapproved posts shouldn't show at all
         # unapproved posts shouldn't show at all
-        unapproved_post = testutils.reply_thread(self.thread, is_unapproved=True)
+        unapproved_post = testutils.reply_thread(
+            self.thread,
+            is_unapproved=True,
+        )
 
 
         response = self.client.get(self.tested_links[1])
         response = self.client.get(self.tested_links[1])
         self.assertNotContains(response, unapproved_post.get_absolute_url())
         self.assertNotContains(response, unapproved_post.get_absolute_url())

+ 53 - 74
misago/threads/tests/test_threads_editor_api.py

@@ -63,8 +63,8 @@ class EditorApiTestCase(AuthenticatedUserTestCase):
             self.user, {
             self.user, {
                 'browseable_categories': browseable_categories,
                 'browseable_categories': browseable_categories,
                 'categories': {
                 'categories': {
-                    self.category.pk: final_acl
-                }
+                    self.category.pk: final_acl,
+                },
             }
             }
         )
         )
 
 
@@ -91,19 +91,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
 
 
     def test_category_disallowing_new_threads(self):
     def test_category_disallowing_new_threads(self):
         """endpoint omits category disallowing starting threads"""
         """endpoint omits category disallowing starting threads"""
-        self.override_acl({
-            'can_start_threads': 0,
-        })
+        self.override_acl({'can_start_threads': 0})
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertContains(response, "No categories that allow new threads", status_code=403)
         self.assertContains(response, "No categories that allow new threads", status_code=403)
 
 
     def test_category_closed_disallowing_new_threads(self):
     def test_category_closed_disallowing_new_threads(self):
         """endpoint omits closed category"""
         """endpoint omits closed category"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_close_threads': 0,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_close_threads': 0})
 
 
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
@@ -113,10 +108,7 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
 
 
     def test_category_closed_allowing_new_threads(self):
     def test_category_closed_allowing_new_threads(self):
         """endpoint adds closed category that allows new threads"""
         """endpoint adds closed category that allows new threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_close_threads': 1,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_close_threads': 1})
 
 
         self.category.is_closed = True
         self.category.is_closed = True
         self.category.save()
         self.category.save()
@@ -133,16 +125,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': True,
                     'close': True,
                     'hide': False,
                     'hide': False,
-                    'pin': 0
-                }
+                    'pin': 0,
+                },
             }
             }
         )
         )
 
 
     def test_category_allowing_new_threads(self):
     def test_category_allowing_new_threads(self):
         """endpoint adds category that allows new threads"""
         """endpoint adds category that allows new threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-        })
+        self.override_acl({'can_start_threads': 2})
 
 
         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)
@@ -156,17 +146,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': False,
                     'close': False,
                     'hide': False,
                     'hide': False,
-                    'pin': 0
-                }
+                    'pin': 0,
+                },
             }
             }
         )
         )
 
 
     def test_category_allowing_closing_threads(self):
     def test_category_allowing_closing_threads(self):
         """endpoint adds category that allows new closed threads"""
         """endpoint adds category that allows new closed threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_close_threads': 1,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_close_threads': 1})
 
 
         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)
@@ -180,17 +167,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': True,
                     'close': True,
                     'hide': False,
                     'hide': False,
-                    'pin': 0
-                }
+                    'pin': 0,
+                },
             }
             }
         )
         )
 
 
     def test_category_allowing_locally_pinned_threads(self):
     def test_category_allowing_locally_pinned_threads(self):
         """endpoint adds category that allows locally pinned threads"""
         """endpoint adds category that allows locally pinned threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_pin_threads': 1,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_pin_threads': 1})
 
 
         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)
@@ -204,17 +188,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': False,
                     'close': False,
                     'hide': False,
                     'hide': False,
-                    'pin': 1
-                }
+                    'pin': 1,
+                },
             }
             }
         )
         )
 
 
     def test_category_allowing_globally_pinned_threads(self):
     def test_category_allowing_globally_pinned_threads(self):
         """endpoint adds category that allows globally pinned threads"""
         """endpoint adds category that allows globally pinned threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_pin_threads': 2,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_pin_threads': 2})
 
 
         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)
@@ -228,17 +209,14 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': False,
                     'close': False,
                     'hide': False,
                     'hide': False,
-                    'pin': 2
-                }
+                    'pin': 2,
+                },
             }
             }
         )
         )
 
 
     def test_category_allowing_hidden_threads(self):
     def test_category_allowing_hidden_threads(self):
         """endpoint adds category that allows globally pinned threads"""
         """endpoint adds category that allows globally pinned threads"""
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_hide_threads': 1,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_hide_threads': 1})
 
 
         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)
@@ -252,15 +230,12 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': 0,
                     'close': 0,
                     'hide': 1,
                     'hide': 1,
-                    'pin': 0
-                }
+                    'pin': 0,
+                },
             }
             }
         )
         )
 
 
-        self.override_acl({
-            'can_start_threads': 2,
-            'can_hide_threads': 2,
-        })
+        self.override_acl({'can_start_threads': 2, 'can_hide_threads': 2})
 
 
         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)
@@ -274,8 +249,8 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
                 'post': {
                 'post': {
                     'close': False,
                     'close': False,
                     'hide': True,
                     'hide': True,
-                    'pin': 0
-                }
+                    'pin': 0,
+                },
             }
             }
         )
         )
 
 
@@ -286,7 +261,9 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
 
         self.thread = testutils.post_thread(category=self.category)
         self.thread = testutils.post_thread(category=self.category)
         self.api_link = reverse(
         self.api_link = reverse(
-            'misago:api:thread-post-editor', kwargs={'thread_pk': self.thread.pk}
+            'misago:api:thread-post-editor', kwargs={
+                'thread_pk': self.thread.pk,
+            }
         )
         )
 
 
     def test_anonymous_user_request(self):
     def test_anonymous_user_request(self):
@@ -369,7 +346,10 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
         self.override_acl({'can_reply_threads': 1})
         self.override_acl({'can_reply_threads': 1})
 
 
         # unapproved reply can't be replied to
         # unapproved reply can't be replied to
-        unapproved_reply = testutils.reply_thread(self.thread, is_unapproved=True)
+        unapproved_reply = testutils.reply_thread(
+            self.thread,
+            is_unapproved=True,
+        )
 
 
         response = self.client.get('{}?reply={}'.format(self.api_link, unapproved_reply.pk))
         response = self.client.get('{}?reply={}'.format(self.api_link, unapproved_reply.pk))
         self.assertEqual(response.status_code, 404)
         self.assertEqual(response.status_code, 404)
@@ -410,10 +390,11 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(
         self.assertEqual(
-            response.json(),
-            {'id': reply_to.pk,
-             'post': reply_to.original,
-             'poster': reply_to.poster_name}
+            response.json(), {
+                'id': reply_to.pk,
+                'post': reply_to.original,
+                'poster': reply_to.poster_name,
+            }
         )
         )
 
 
 
 
@@ -426,8 +407,10 @@ class EditReplyEditorApiTests(EditorApiTestCase):
 
 
         self.api_link = reverse(
         self.api_link = reverse(
             'misago:api:thread-post-editor',
             'misago:api:thread-post-editor',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.post.pk,
+            }
         )
         )
 
 
     def test_anonymous_user_request(self):
     def test_anonymous_user_request(self):
@@ -560,9 +543,7 @@ class EditReplyEditorApiTests(EditorApiTestCase):
 
 
     def test_other_user_post(self):
     def test_other_user_post(self):
         """api validates if other user's post can be edited"""
         """api validates if other user's post can be edited"""
-        self.override_acl({
-            'can_edit_posts': 1,
-        })
+        self.override_acl({'can_edit_posts': 1})
 
 
         self.post.poster = None
         self.post.poster = None
         self.post.save()
         self.post.save()
@@ -573,9 +554,7 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         )
         )
 
 
         # allow other users post edition
         # allow other users post edition
-        self.override_acl({
-            'can_edit_posts': 2,
-        })
+        self.override_acl({'can_edit_posts': 2})
 
 
         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)
@@ -591,8 +570,10 @@ class EditReplyEditorApiTests(EditorApiTestCase):
 
 
         api_link = reverse(
         api_link = reverse(
             'misago:api:thread-post-editor',
             'misago:api:thread-post-editor',
-            kwargs={'thread_pk': self.thread.pk,
-                    'pk': self.thread.first_post.pk}
+            kwargs={
+                'thread_pk': self.thread.pk,
+                'pk': self.thread.first_post.pk,
+            }
         )
         )
 
 
         response = self.client.get(api_link)
         response = self.client.get(api_link)
@@ -601,13 +582,13 @@ class EditReplyEditorApiTests(EditorApiTestCase):
     def test_edit(self):
     def test_edit(self):
         """endpoint returns valid configuration for editor"""
         """endpoint returns valid configuration for editor"""
         for _ in range(3):
         for _ in range(3):
-            self.override_acl({
-                'max_attachment_size': 1000,
-            })
+            self.override_acl({'max_attachment_size': 1000})
 
 
             with open(TEST_DOCUMENT_PATH, 'rb') as upload:
             with open(TEST_DOCUMENT_PATH, 'rb') as upload:
                 response = self.client.post(
                 response = self.client.post(
-                    reverse('misago:api:attachment-list'), data={'upload': upload}
+                    reverse('misago:api:attachment-list'), data={
+                        'upload': upload,
+                    }
                 )
                 )
             self.assertEqual(response.status_code, 200)
             self.assertEqual(response.status_code, 200)
 
 
@@ -620,9 +601,7 @@ class EditReplyEditorApiTests(EditorApiTestCase):
             attachment.post = self.post
             attachment.post = self.post
             attachment.save()
             attachment.save()
 
 
-        self.override_acl({
-            'can_edit_posts': 1,
-        })
+        self.override_acl({'can_edit_posts': 1})
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
 
 
         for attachment in attachments:
         for attachment in attachments:
@@ -646,6 +625,6 @@ class EditReplyEditorApiTests(EditorApiTestCase):
                 'attachments': [
                 'attachments': [
                     AttachmentSerializer(attachments[1], context={'user': self.user}).data,
                     AttachmentSerializer(attachments[1], context={'user': self.user}).data,
                     AttachmentSerializer(attachments[0], context={'user': self.user}).data,
                     AttachmentSerializer(attachments[0], context={'user': self.user}).data,
-                ]
+                ],
             }
             }
         )
         )

+ 139 - 109
misago/threads/tests/test_threads_merge_api.py

@@ -21,7 +21,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             name='Category B',
             name='Category B',
             slug='category-b',
             slug='category-b',
         ).insert_at(
         ).insert_at(
-            self.category, position='last-child', save=True
+            self.category,
+            position='last-child',
+            save=True,
         )
         )
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
 
 
@@ -32,49 +34,61 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "You have to select at least two threads to merge."}
+            response_json, {
+                'detail': "You have to select at least two threads to merge.",
+            }
         )
         )
 
 
     def test_merge_empty_threads(self):
     def test_merge_empty_threads(self):
         """api validates if we are trying to empty threads list"""
         """api validates if we are trying to empty threads list"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'threads': []
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'threads': [],
+            }),
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "You have to select at least two threads to merge."}
+            response_json, {
+                'detail': "You have to select at least two threads to merge.",
+            }
         )
         )
 
 
     def test_merge_invalid_threads(self):
     def test_merge_invalid_threads(self):
         """api validates if we are trying to merge invalid thread ids"""
         """api validates if we are trying to merge invalid thread ids"""
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'threads': 'abcd'
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'threads': 'abcd',
+            }),
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "One or more thread ids received were invalid."}
+            response_json, {
+                'detail': "One or more thread ids received were invalid.",
+            }
         )
         )
 
 
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': ['a', '-', 'c']
+                'threads': ['a', '-', 'c'],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "One or more thread ids received were invalid."}
+            response_json, {
+                'detail': "One or more thread ids received were invalid.",
+            }
         )
         )
 
 
     def test_merge_single_thread(self):
     def test_merge_single_thread(self):
@@ -82,15 +96,17 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': [self.thread.id]
+                'threads': [self.thread.id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "You have to select at least two threads to merge."}
+            response_json, {
+                'detail': "You have to select at least two threads to merge.",
+            }
         )
         )
 
 
     def test_merge_with_nonexisting_thread(self):
     def test_merge_with_nonexisting_thread(self):
@@ -100,15 +116,17 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': [self.thread.id, self.thread.id + 1000]
+                'threads': [self.thread.id, self.thread.id + 1000],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "One or more threads to merge could not be found."}
+            response_json, {
+                'detail': "One or more threads to merge could not be found.",
+            }
         )
         )
 
 
     def test_merge_with_invisible_thread(self):
     def test_merge_with_invisible_thread(self):
@@ -118,15 +136,17 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': [self.thread.id, unaccesible_thread.id]
+                'threads': [self.thread.id, unaccesible_thread.id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'detail': "One or more threads to merge could not be found."}
+            response_json, {
+                'detail': "One or more threads to merge could not be found.",
+            }
         )
         )
 
 
     def test_merge_no_permission(self):
     def test_merge_no_permission(self):
@@ -136,9 +156,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': [self.thread.id, thread.id]
+                'threads': [self.thread.id, thread.id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
@@ -148,12 +168,12 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 {
                 {
                     'id': thread.pk,
                     'id': thread.pk,
                     'title': thread.title,
                     'title': thread.title,
-                    'errors': ["You don't have permission to merge this thread with others."]
+                    'errors': ["You don't have permission to merge this thread with others."],
                 },
                 },
                 {
                 {
                     'id': self.thread.pk,
                     'id': self.thread.pk,
                     'title': self.thread.title,
                     'title': self.thread.title,
-                    'errors': ["You don't have permission to merge this thread with others."]
+                    'errors': ["You don't have permission to merge this thread with others."],
                 },
                 },
             ]
             ]
         )
         )
@@ -172,16 +192,19 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         })
         })
 
 
         response = self.client.post(
         response = self.client.post(
-            self.api_link, json.dumps({
-                'threads': threads
-            }), content_type="application/json"
+            self.api_link,
+            json.dumps({
+                'threads': threads,
+            }),
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'detail': "No more than %s threads can be merged at single time." % MERGE_LIMIT}
+            response_json, {
+                'detail': "No more than %s threads can be merged at single time." % MERGE_LIMIT,
+            }
         )
         )
 
 
     def test_merge_no_final_thread(self):
     def test_merge_no_final_thread(self):
@@ -198,9 +221,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         response = self.client.post(
         response = self.client.post(
             self.api_link,
             self.api_link,
             json.dumps({
             json.dumps({
-                'threads': [self.thread.id, thread.id]
+                'threads': [self.thread.id, thread.id],
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
@@ -230,14 +253,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': '$$$',
                 'title': '$$$',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_merge_invalid_category(self):
     def test_merge_invalid_category(self):
@@ -258,12 +282,16 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Valid thread title',
                 'title': 'Valid thread title',
                 'category': self.category_b.id,
                 'category': self.category_b.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
-        self.assertEqual(response_json, {'category': ["Requested category could not be found."]})
+        self.assertEqual(
+            response_json, {
+                'category': ["Requested category could not be found."],
+            }
+        )
 
 
     def test_merge_unallowed_start_thread(self):
     def test_merge_unallowed_start_thread(self):
         """api rejects merge because category isn't allowing starting threads"""
         """api rejects merge because category isn't allowing starting threads"""
@@ -272,7 +300,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             'can_close_threads': False,
             'can_close_threads': False,
             'can_edit_threads': False,
             'can_edit_threads': False,
             'can_reply_threads': False,
             'can_reply_threads': False,
-            'can_start_threads': 0
+            'can_start_threads': 0,
         })
         })
 
 
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
@@ -282,15 +310,17 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             json.dumps({
             json.dumps({
                 'threads': [self.thread.id, thread.id],
                 'threads': [self.thread.id, thread.id],
                 'title': 'Valid thread title',
                 'title': 'Valid thread title',
-                'category': self.category.id
+                'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'category': ["You can't create new threads in selected category."]}
+            response_json, {
+                'category': ["You can't create new threads in selected category."],
+            }
         )
         )
 
 
     def test_merge_invalid_weight(self):
     def test_merge_invalid_weight(self):
@@ -312,13 +342,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 4,
                 'weight': 4,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json, {'weight': ["Ensure this value is less than or equal to 2."]}
+            response_json, {
+                'weight': ["Ensure this value is less than or equal to 2."],
+            }
         )
         )
 
 
     def test_merge_unallowed_global_weight(self):
     def test_merge_unallowed_global_weight(self):
@@ -340,14 +372,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 2,
                 'weight': 2,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'weight': ["You don't have permission to pin threads globally in this category."]}
+            response_json, {
+                'weight': ["You don't have permission to pin threads globally in this category."],
+            }
         )
         )
 
 
     def test_merge_unallowed_local_weight(self):
     def test_merge_unallowed_local_weight(self):
@@ -369,14 +402,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 1,
                 'weight': 1,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'weight': ["You don't have permission to pin threads in this category."]}
+            response_json, {
+                'weight': ["You don't have permission to pin threads in this category."],
+            }
         )
         )
 
 
     def test_merge_allowed_local_weight(self):
     def test_merge_allowed_local_weight(self):
@@ -399,14 +433,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 1,
                 'weight': 1,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_merge_allowed_global_weight(self):
     def test_merge_allowed_global_weight(self):
@@ -429,14 +464,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'weight': 2,
                 'weight': 2,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_merge_unallowed_close(self):
     def test_merge_unallowed_close(self):
@@ -458,14 +494,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'is_closed': True,
                 'is_closed': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'is_closed': ["You don't have permission to close threads in this category."]}
+            response_json, {
+                'is_closed': ["You don't have permission to close threads in this category."],
+            }
         )
         )
 
 
     def test_merge_with_close(self):
     def test_merge_with_close(self):
@@ -488,14 +525,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'weight': 0,
                 'weight': 0,
                 'is_closed': True,
                 'is_closed': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_merge_unallowed_hidden(self):
     def test_merge_unallowed_hidden(self):
@@ -518,14 +556,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'is_hidden': True,
                 'is_hidden': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'is_hidden': ["You don't have permission to hide threads in this category."]}
+            response_json, {
+                'is_hidden': ["You don't have permission to hide threads in this category."],
+            }
         )
         )
 
 
     def test_merge_with_hide(self):
     def test_merge_with_hide(self):
@@ -549,14 +588,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'weight': 0,
                 'weight': 0,
                 'is_hidden': True,
                 'is_hidden': True,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
 
 
         response_json = response.json()
         response_json = response.json()
         self.assertEqual(
         self.assertEqual(
-            response_json,
-            {'title': ["Thread title should be at least 5 characters long (it has 3)."]}
+            response_json, {
+                'title': ["Thread title should be at least 5 characters long (it has 3)."],
+            }
         )
         )
 
 
     def test_merge(self):
     def test_merge(self):
@@ -579,7 +619,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -611,7 +651,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
             'can_merge_threads': True,
             'can_merge_threads': True,
             'can_close_threads': True,
             'can_close_threads': True,
             'can_hide_threads': 1,
             'can_hide_threads': 1,
-            'can_pin_threads': 2
+            'can_pin_threads': 2,
         })
         })
 
 
         thread = testutils.post_thread(category=self.category)
         thread = testutils.post_thread(category=self.category)
@@ -624,9 +664,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'category': self.category.id,
                 'category': self.category.id,
                 'is_closed': 1,
                 'is_closed': 1,
                 'is_hidden': 1,
                 'is_hidden': 1,
-                'weight': 2
+                'weight': 2,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -675,7 +715,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -701,9 +741,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_merge_threads_kept_poll(self):
     def test_merge_threads_kept_poll(self):
         """api merges two threads successfully, keeping poll from old thread"""
         """api merges two threads successfully, keeping poll from old thread"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(other_thread, self.user)
         poll = testutils.post_poll(other_thread, self.user)
@@ -715,7 +753,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -731,9 +769,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_merge_threads_moved_poll(self):
     def test_merge_threads_moved_poll(self):
         """api merges two threads successfully, moving poll from other thread"""
         """api merges two threads successfully, moving poll from other thread"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
@@ -745,7 +781,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -761,9 +797,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_threads_merge_conflict(self):
     def test_threads_merge_conflict(self):
         """api errors on merge conflict, returning list of available polls"""
         """api errors on merge conflict, returning list of available polls"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
@@ -776,15 +810,17 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(), {
             response.json(), {
-                'polls': [[0, "Delete all polls"],
-                          [poll.pk, poll.question],
-                          [other_poll.pk, other_poll.question]]
+                'polls': [
+                    [0, "Delete all polls"],
+                    [poll.pk, poll.question],
+                    [other_poll.pk, other_poll.question],
+                ],
             }
             }
         )
         )
 
 
@@ -794,9 +830,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_threads_merge_conflict_invalid_resolution(self):
     def test_threads_merge_conflict_invalid_resolution(self):
         """api errors on invalid merge conflict resolution"""
         """api errors on invalid merge conflict resolution"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
@@ -809,13 +843,15 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
-                'poll': 'dsa7dsadsa9789'
+                'poll': 'dsa7dsadsa9789',
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
 
 
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'detail': "Invalid choice."})
+        self.assertEqual(response.json(), {
+            'detail': "Invalid choice.",
+        })
 
 
         # polls and votes were untouched
         # polls and votes were untouched
         self.assertEqual(Poll.objects.count(), 2)
         self.assertEqual(Poll.objects.count(), 2)
@@ -823,9 +859,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_threads_merge_conflict_delete_all(self):
     def test_threads_merge_conflict_delete_all(self):
         """api deletes all polls when delete all choice is selected"""
         """api deletes all polls when delete all choice is selected"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
 
 
@@ -838,9 +872,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
-                'poll': 0
+                'poll': 0,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -850,9 +884,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_threads_merge_conflict_keep_first_poll(self):
     def test_threads_merge_conflict_keep_first_poll(self):
         """api deletes other poll on merge"""
         """api deletes other poll on merge"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
@@ -864,9 +896,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
-                'poll': poll.pk
+                'poll': poll.pk,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
@@ -880,9 +912,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_threads_merge_conflict_keep_other_poll(self):
     def test_threads_merge_conflict_keep_other_poll(self):
         """api deletes first poll on merge"""
         """api deletes first poll on merge"""
-        self.override_acl({
-            'can_merge_threads': True,
-        })
+        self.override_acl({'can_merge_threads': True})
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
         poll = testutils.post_poll(self.thread, self.user)
         poll = testutils.post_poll(self.thread, self.user)
@@ -894,9 +924,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                 'threads': [self.thread.id, other_thread.id],
                 'threads': [self.thread.id, other_thread.id],
                 'title': 'Merged thread!',
                 'title': 'Merged thread!',
                 'category': self.category.id,
                 'category': self.category.id,
-                'poll': other_poll.pk
+                'poll': other_poll.pk,
             }),
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         )
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 

+ 3 - 1
misago/threads/tests/test_threads_moderation.py

@@ -142,7 +142,9 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
             name='New Category',
             name='New Category',
             slug='new-category',
             slug='new-category',
         ).insert_at(
         ).insert_at(
-            root_category, position='last-child', save=True
+            root_category,
+            position='last-child',
+            save=True,
         )
         )
         new_category = Category.objects.get(slug='new-category')
         new_category = Category.objects.get(slug='new-category')
 
 

+ 91 - 45
misago/threads/tests/test_threadslists.py

@@ -42,14 +42,18 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
             slug='category-a',
             slug='category-a',
             css_class='showing-category-a',
             css_class='showing-category-a',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
         Category(
         Category(
             name='Category E',
             name='Category E',
             slug='category-e',
             slug='category-e',
             css_class='showing-category-e',
             css_class='showing-category-e',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.root = Category.objects.root_category()
         self.root = Category.objects.root_category()
@@ -61,7 +65,9 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
             slug='category-b',
             slug='category-b',
             css_class='showing-category-b',
             css_class='showing-category-b',
         ).insert_at(
         ).insert_at(
-            self.category_a, position='last-child', save=True
+            self.category_a,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
@@ -71,14 +77,18 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
             slug='category-c',
             slug='category-c',
             css_class='showing-category-c',
             css_class='showing-category-c',
         ).insert_at(
         ).insert_at(
-            self.category_b, position='last-child', save=True
+            self.category_b,
+            position='last-child',
+            save=True,
         )
         )
         Category(
         Category(
             name='Category D',
             name='Category D',
             slug='category-d',
             slug='category-d',
             css_class='showing-category-d',
             css_class='showing-category-d',
         ).insert_at(
         ).insert_at(
-            self.category_b, position='last-child', save=True
+            self.category_b,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.category_c = Category.objects.get(slug='category-c')
         self.category_c = Category.objects.get(slug='category-c')
@@ -90,7 +100,9 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
             slug='category-f',
             slug='category-f',
             css_class='showing-category-f',
             css_class='showing-category-f',
         ).insert_at(
         ).insert_at(
-            self.category_e, position='last-child', save=True
+            self.category_e,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.category_f = Category.objects.get(slug='category-f')
         self.category_f = Category.objects.get(slug='category-f')
@@ -116,7 +128,7 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
             'categories': {},
             'categories': {},
             'visible_categories': [],
             'visible_categories': [],
             'browseable_categories': [],
             'browseable_categories': [],
-            'can_approve_content': []
+            'can_approve_content': [],
         }
         }
 
 
         # copy first category's acl to other categories to make base for overrides
         # copy first category's acl to other categories to make base for overrides
@@ -136,7 +148,7 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
                 'can_see_all_threads': 1,
                 'can_see_all_threads': 1,
                 'can_see_own_threads': 0,
                 'can_see_own_threads': 0,
                 'can_hide_threads': 0,
                 'can_hide_threads': 0,
-                'can_approve_content': 0
+                'can_approve_content': 0,
             })
             })
 
 
             if category_acl:
             if category_acl:
@@ -238,7 +250,9 @@ class AllThreadsListTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
 
 
@@ -352,6 +366,8 @@ class AllThreadsListTests(ThreadsListTestCase):
 
 
     def test_noscript_pagination(self):
     def test_noscript_pagination(self):
         """threads list is paginated for users with js disabled"""
         """threads list is paginated for users with js disabled"""
+        threads_per_page = settings.MISAGO_THREADS_PER_PAGE
+
         threads = []
         threads = []
         for _ in range(settings.MISAGO_THREADS_PER_PAGE * 3):
         for _ in range(settings.MISAGO_THREADS_PER_PAGE * 3):
             threads.append(testutils.post_thread(category=self.first_category))
             threads.append(testutils.post_thread(category=self.first_category))
@@ -360,12 +376,11 @@ class AllThreadsListTests(ThreadsListTestCase):
         response = self.client.get('/?page=2')
         response = self.client.get('/?page=2')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        for thread in threads[:settings.MISAGO_THREADS_PER_PAGE]:
+        for thread in threads[:threads_per_page]:
             self.assertNotContains(response, thread.get_absolute_url())
             self.assertNotContains(response, thread.get_absolute_url())
-        for thread in threads[settings.MISAGO_THREADS_PER_PAGE:settings.MISAGO_THREADS_PER_PAGE * 2
-                              ]:
+        for thread in threads[threads_per_page:threads_per_page * 2]:
             self.assertContains(response, thread.get_absolute_url())
             self.assertContains(response, thread.get_absolute_url())
-        for thread in threads[settings.MISAGO_THREADS_PER_PAGE * 2:]:
+        for thread in threads[threads_per_page * 2:]:
             self.assertNotContains(response, thread.get_absolute_url())
             self.assertNotContains(response, thread.get_absolute_url())
 
 
         self.assertNotContains(response, '/?page=1')
         self.assertNotContains(response, '/?page=1')
@@ -375,9 +390,9 @@ class AllThreadsListTests(ThreadsListTestCase):
         response = self.client.get('/?page=3')
         response = self.client.get('/?page=3')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        for thread in threads[settings.MISAGO_THREADS_PER_PAGE:]:
+        for thread in threads[threads_per_page:]:
             self.assertNotContains(response, thread.get_absolute_url())
             self.assertNotContains(response, thread.get_absolute_url())
-        for thread in threads[:settings.MISAGO_THREADS_PER_PAGE]:
+        for thread in threads[:threads_per_page]:
             self.assertContains(response, thread.get_absolute_url())
             self.assertContains(response, thread.get_absolute_url())
 
 
         self.assertContains(response, '/?page=2')
         self.assertContains(response, '/?page=2')
@@ -395,7 +410,9 @@ class CategoryThreadsListTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
 
 
@@ -412,7 +429,9 @@ class CategoryThreadsListTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
 
 
@@ -425,8 +444,8 @@ class CategoryThreadsListTests(ThreadsListTestCase):
                         test_category.pk: {
                         test_category.pk: {
                             'can_see': 1,
                             'can_see': 1,
                             'can_browse': 0,
                             'can_browse': 0,
-                        }
-                    }
+                        },
+                    },
                 }
                 }
             )
             )
 
 
@@ -441,8 +460,8 @@ class CategoryThreadsListTests(ThreadsListTestCase):
                         test_category.pk: {
                         test_category.pk: {
                             'can_see': 1,
                             'can_see': 1,
                             'can_browse': 0,
                             'can_browse': 0,
-                        }
-                    }
+                        },
+                    },
                 }
                 }
             )
             )
 
 
@@ -568,7 +587,9 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
 
 
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
@@ -585,7 +606,9 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
 
 
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
@@ -786,9 +809,7 @@ class MyThreadsListTests(ThreadsListTestCase):
             poster=self.user,
             poster=self.user,
         )
         )
 
 
-        other_thread = testutils.post_thread(
-            category=self.category_a,
-        )
+        other_thread = testutils.post_thread(category=self.category_a)
 
 
         self.access_all_categories()
         self.access_all_categories()
 
 
@@ -853,9 +874,7 @@ class NewThreadsListTests(ThreadsListTestCase):
 
 
     def test_list_renders_new_thread(self):
     def test_list_renders_new_thread(self):
         """list renders new thread"""
         """list renders new thread"""
-        test_thread = testutils.post_thread(
-            category=self.category_a,
-        )
+        test_thread = testutils.post_thread(category=self.category_a)
 
 
         self.access_all_categories()
         self.access_all_categories()
 
 
@@ -892,10 +911,14 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.save()
         self.user.save()
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
-            category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
+            category=self.category_a,
+            started_on=self.user.joined_on - timedelta(days=2),
         )
         )
 
 
-        testutils.reply_thread(test_thread, posted_on=self.user.joined_on + timedelta(days=4))
+        testutils.reply_thread(
+            test_thread,
+            posted_on=self.user.joined_on + timedelta(days=4),
+        )
 
 
         self.access_all_categories()
         self.access_all_categories()
 
 
@@ -933,7 +956,7 @@ class NewThreadsListTests(ThreadsListTestCase):
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
             category=self.category_a,
             category=self.category_a,
-            started_on=timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 1)
+            started_on=timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 1),
         )
         )
 
 
         self.access_all_categories()
         self.access_all_categories()
@@ -969,7 +992,8 @@ class NewThreadsListTests(ThreadsListTestCase):
         self.user.save()
         self.user.save()
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
-            category=self.category_a, started_on=self.user.joined_on - timedelta(minutes=1)
+            category=self.category_a,
+            started_on=self.user.joined_on - timedelta(minutes=1),
         )
         )
 
 
         self.access_all_categories()
         self.access_all_categories()
@@ -1233,7 +1257,7 @@ class UnreadThreadsListTests(ThreadsListTestCase):
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
             category=self.category_a,
             category=self.category_a,
-            started_on=timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 5)
+            started_on=timezone.now() - timedelta(days=settings.MISAGO_READTRACKER_CUTOFF + 5),
         )
         )
 
 
         threadstracker.make_thread_read_aware(self.user, test_thread)
         threadstracker.make_thread_read_aware(self.user, test_thread)
@@ -1276,13 +1300,17 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.save()
         self.user.save()
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
-            category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
+            category=self.category_a,
+            started_on=self.user.joined_on - timedelta(days=2),
         )
         )
 
 
         threadstracker.make_thread_read_aware(self.user, test_thread)
         threadstracker.make_thread_read_aware(self.user, test_thread)
         threadstracker.read_thread(self.user, test_thread, test_thread.last_post)
         threadstracker.read_thread(self.user, test_thread, test_thread.last_post)
 
 
-        testutils.reply_thread(test_thread, posted_on=test_thread.started_on + timedelta(days=1))
+        testutils.reply_thread(
+            test_thread,
+            posted_on=test_thread.started_on + timedelta(days=1),
+        )
 
 
         self.access_all_categories()
         self.access_all_categories()
 
 
@@ -1319,7 +1347,8 @@ class UnreadThreadsListTests(ThreadsListTestCase):
         self.user.save()
         self.user.save()
 
 
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
-            category=self.category_a, started_on=self.user.joined_on - timedelta(days=2)
+            category=self.category_a,
+            started_on=self.user.joined_on - timedelta(days=2),
         )
         )
 
 
         threadstracker.make_thread_read_aware(self.user, test_thread)
         threadstracker.make_thread_read_aware(self.user, test_thread)
@@ -1462,7 +1491,9 @@ class UnapprovedListTests(ThreadsListTestCase):
 
 
         # approval perm has no influence on visibility
         # approval perm has no influence on visibility
         for test_url in TEST_URLS:
         for test_url in TEST_URLS:
-            self.access_all_categories(base_acl={'can_see_unapproved_content_lists': True})
+            self.access_all_categories(base_acl={
+                'can_see_unapproved_content_lists': True,
+            })
 
 
             self.access_all_categories()
             self.access_all_categories()
             response = self.client.get(test_url)
             response = self.client.get(test_url)
@@ -1481,8 +1512,11 @@ class UnapprovedListTests(ThreadsListTestCase):
         )
         )
 
 
         self.access_all_categories({
         self.access_all_categories({
-            'can_approve_content': True
-        }, {'can_see_unapproved_content_lists': True})
+            'can_approve_content': True,
+        }, {
+            'can_see_unapproved_content_lists': True,
+        })
+
         response = self.client.get('/unapproved/')
         response = self.client.get('/unapproved/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())
@@ -1490,7 +1524,10 @@ class UnapprovedListTests(ThreadsListTestCase):
 
 
         self.access_all_categories({
         self.access_all_categories({
             'can_approve_content': True
             'can_approve_content': True
-        }, {'can_see_unapproved_content_lists': True})
+        }, {
+            'can_see_unapproved_content_lists': True,
+        })
+
         response = self.client.get(self.category_a.get_absolute_url() + 'unapproved/')
         response = self.client.get(self.category_a.get_absolute_url() + 'unapproved/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())
@@ -1499,7 +1536,10 @@ class UnapprovedListTests(ThreadsListTestCase):
         # test api
         # test api
         self.access_all_categories({
         self.access_all_categories({
             'can_approve_content': True
             'can_approve_content': True
-        }, {'can_see_unapproved_content_lists': True})
+        }, {
+            'can_see_unapproved_content_lists': True,
+        })
+
         response = self.client.get('%s?list=unapproved' % self.api_link)
         response = self.client.get('%s?list=unapproved' % self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())
@@ -1520,20 +1560,26 @@ class UnapprovedListTests(ThreadsListTestCase):
             is_unapproved=True,
             is_unapproved=True,
         )
         )
 
 
-        self.access_all_categories(base_acl={'can_see_unapproved_content_lists': True})
+        self.access_all_categories(base_acl={
+            'can_see_unapproved_content_lists': True,
+        })
         response = self.client.get('/unapproved/')
         response = self.client.get('/unapproved/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertNotContains(response, hidden_thread.get_absolute_url())
         self.assertNotContains(response, hidden_thread.get_absolute_url())
 
 
-        self.access_all_categories(base_acl={'can_see_unapproved_content_lists': True})
+        self.access_all_categories(base_acl={
+            'can_see_unapproved_content_lists': True,
+        })
         response = self.client.get(self.category_a.get_absolute_url() + 'unapproved/')
         response = self.client.get(self.category_a.get_absolute_url() + 'unapproved/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertNotContains(response, hidden_thread.get_absolute_url())
         self.assertNotContains(response, hidden_thread.get_absolute_url())
 
 
         # test api
         # test api
-        self.access_all_categories(base_acl={'can_see_unapproved_content_lists': True})
+        self.access_all_categories(base_acl={
+            'can_see_unapproved_content_lists': True,
+        })
         response = self.client.get('%s?list=unapproved' % self.api_link)
         response = self.client.get('%s?list=unapproved' % self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, visible_thread.get_absolute_url())
         self.assertContains(response, visible_thread.get_absolute_url())

+ 18 - 18
misago/threads/tests/test_threadview.py

@@ -41,7 +41,11 @@ class ThreadViewTestCase(AuthenticatedUserTestCase):
         if acl:
         if acl:
             category_acl.update(acl)
             category_acl.update(acl)
 
 
-        override_acl(self.user, {'categories': {self.category.pk: category_acl}})
+        override_acl(self.user, {
+            'categories': {
+                self.category.pk: category_acl,
+            },
+        })
 
 
 
 
 class ThreadVisibilityTests(ThreadViewTestCase):
 class ThreadVisibilityTests(ThreadViewTestCase):
@@ -231,10 +235,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
         self.thread.save()
         self.thread.save()
 
 
         for action, message in TEST_ACTIONS:
         for action, message in TEST_ACTIONS:
-            self.override_acl({
-                'can_approve_content': 1,
-                'can_hide_threads': 1,
-            })
+            self.override_acl({'can_approve_content': 1, 'can_hide_threads': 1})
 
 
             self.thread.post_set.filter(is_event=True).delete()
             self.thread.post_set.filter(is_event=True).delete()
             action(MockRequest(self.user), self.thread)
             action(MockRequest(self.user), self.thread)
@@ -248,10 +249,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
 
 
             # hidden events don't render without permission
             # hidden events don't render without permission
             hide_post(self.user, event)
             hide_post(self.user, event)
-            self.override_acl({
-                'can_approve_content': 1,
-                'can_hide_threads': 1,
-            })
+            self.override_acl({'can_approve_content': 1, 'can_hide_threads': 1})
 
 
             response = self.client.get(self.thread.get_absolute_url())
             response = self.client.get(self.thread.get_absolute_url())
             self.assertNotContains(response, event.get_absolute_url())
             self.assertNotContains(response, event.get_absolute_url())
@@ -398,7 +396,7 @@ class ThreadAttachmentsViewTests(ThreadViewTestCase):
             'filetype': 'ZIP',
             'filetype': 'ZIP',
             'is_image': False,
             'is_image': False,
             'uploaded_on': '2016-10-22T21:17:40.408710Z',
             'uploaded_on': '2016-10-22T21:17:40.408710Z',
-            'uploader_name': 'BobBoberson'
+            'uploader_name': 'BobBoberson',
         }
         }
 
 
         json.update(data)
         json.update(data)
@@ -413,25 +411,27 @@ class ThreadAttachmentsViewTests(ThreadViewTestCase):
                 'url': {
                 'url': {
                     'index': '/attachment/loremipsum-123/',
                     'index': '/attachment/loremipsum-123/',
                     'thumb': None,
                     'thumb': None,
-                    'uploader': '/user/bobboberson-123/'
+                    'uploader': '/user/bobboberson-123/',
                 },
                 },
                 'filename': 'Archiwum-1.zip',
                 'filename': 'Archiwum-1.zip',
-            }), self.mock_attachment_cache({
+            }),
+            self.mock_attachment_cache({
                 'url': {
                 'url': {
                     'index': '/attachment/loremipsum-223/',
                     'index': '/attachment/loremipsum-223/',
                     'thumb': '/attachment/thumb/loremipsum-223/',
                     'thumb': '/attachment/thumb/loremipsum-223/',
-                    'uploader': '/user/bobboberson-223/'
+                    'uploader': '/user/bobboberson-223/',
                 },
                 },
                 'is_image': True,
                 'is_image': True,
-                'filename': 'Archiwum-2.zip'
-            }), self.mock_attachment_cache({
+                'filename': 'Archiwum-2.zip',
+            }),
+            self.mock_attachment_cache({
                 'url': {
                 'url': {
                     'index': '/attachment/loremipsum-323/',
                     'index': '/attachment/loremipsum-323/',
                     'thumb': None,
                     'thumb': None,
-                    'uploader': '/user/bobboberson-323/'
+                    'uploader': '/user/bobboberson-323/',
                 },
                 },
-                'filename': 'Archiwum-3.zip'
-            })
+                'filename': 'Archiwum-3.zip',
+            }),
         ]
         ]
         post.save()
         post.save()
 
 

+ 33 - 21
misago/threads/tests/test_utils.py

@@ -27,14 +27,18 @@ class AddCategoriesToItemsTests(MisagoTestCase):
             slug='category-a',
             slug='category-a',
             css_class='showing-category-a',
             css_class='showing-category-a',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
         Category(
         Category(
             name='Category E',
             name='Category E',
             slug='category-e',
             slug='category-e',
             css_class='showing-category-e',
             css_class='showing-category-e',
         ).insert_at(
         ).insert_at(
-            self.root, position='last-child', save=True
+            self.root,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.root = Category.objects.root_category()
         self.root = Category.objects.root_category()
@@ -45,7 +49,9 @@ class AddCategoriesToItemsTests(MisagoTestCase):
             slug='category-b',
             slug='category-b',
             css_class='showing-category-b',
             css_class='showing-category-b',
         ).insert_at(
         ).insert_at(
-            self.category_a, position='last-child', save=True
+            self.category_a,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.category_b = Category.objects.get(slug='category-b')
         self.category_b = Category.objects.get(slug='category-b')
@@ -54,14 +60,18 @@ class AddCategoriesToItemsTests(MisagoTestCase):
             slug='category-c',
             slug='category-c',
             css_class='showing-category-c',
             css_class='showing-category-c',
         ).insert_at(
         ).insert_at(
-            self.category_b, position='last-child', save=True
+            self.category_b,
+            position='last-child',
+            save=True,
         )
         )
         Category(
         Category(
             name='Category D',
             name='Category D',
             slug='category-d',
             slug='category-d',
             css_class='showing-category-d',
             css_class='showing-category-d',
         ).insert_at(
         ).insert_at(
-            self.category_b, position='last-child', save=True
+            self.category_b,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.category_c = Category.objects.get(slug='category-c')
         self.category_c = Category.objects.get(slug='category-c')
@@ -73,7 +83,9 @@ class AddCategoriesToItemsTests(MisagoTestCase):
             slug='category-f',
             slug='category-f',
             css_class='showing-category-f',
             css_class='showing-category-f',
         ).insert_at(
         ).insert_at(
-            self.category_e, position='last-child', save=True
+            self.category_e,
+            position='last-child',
+            save=True,
         )
         )
 
 
         self.clear_state()
         self.clear_state()
@@ -178,62 +190,62 @@ class GetThreadIdFromUrlTests(MisagoTestCase):
                 # perfect match
                 # perfect match
                 'request': MockRequest('https', 'testforum.com', '/discuss/'),
                 'request': MockRequest('https', 'testforum.com', '/discuss/'),
                 'url': 'https://testforum.com/discuss/t/test-thread/123/',
                 'url': 'https://testforum.com/discuss/t/test-thread/123/',
-                'pk': 123
+                'pk': 123,
             },
             },
             {
             {
                 # we don't validate scheme in case site recently moved to https
                 # we don't validate scheme in case site recently moved to https
                 # but user still has old url's saved somewhere
                 # but user still has old url's saved somewhere
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
                 'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
-                'pk': 432
+                'pk': 432,
             },
             },
             {
             {
                 # extract thread id from other thread urls
                 # extract thread id from other thread urls
                 'request': MockRequest('https', 'testforum.com', '/discuss/'),
                 'request': MockRequest('https', 'testforum.com', '/discuss/'),
                 'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
                 'url': 'http://testforum.com/discuss/t/test-thread/432/post/12321/',
-                'pk': 432
+                'pk': 432,
             },
             },
             {
             {
                 # extract thread id from thread page url
                 # extract thread id from thread page url
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'url': 'http://testforum.com/discuss/t/test-thread/432/123/',
                 'url': 'http://testforum.com/discuss/t/test-thread/432/123/',
-                'pk': 432
+                'pk': 432,
             },
             },
             {
             {
                 # extract thread id from thread last post url with relative schema
                 # extract thread id from thread last post url with relative schema
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'url': '//testforum.com/discuss/t/test-thread/18/last/',
                 'url': '//testforum.com/discuss/t/test-thread/18/last/',
-                'pk': 18
+                'pk': 18,
             },
             },
             {
             {
                 # extract thread id from url that lacks scheme
                 # extract thread id from url that lacks scheme
                 'request': MockRequest('http', 'testforum.com', ''),
                 'request': MockRequest('http', 'testforum.com', ''),
                 'url': 'testforum.com/t/test-thread/12/last/',
                 'url': 'testforum.com/t/test-thread/12/last/',
-                'pk': 12
+                'pk': 12,
             },
             },
             {
             {
                 # extract thread id from schemaless thread last post url
                 # extract thread id from schemaless thread last post url
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'request': MockRequest('http', 'testforum.com', '/discuss/'),
                 'url': 'testforum.com/discuss/t/test-thread/18/last/',
                 'url': 'testforum.com/discuss/t/test-thread/18/last/',
-                'pk': 18
+                'pk': 18,
             },
             },
             {
             {
                 # extract thread id from url that lacks scheme and hostname
                 # extract thread id from url that lacks scheme and hostname
                 'request': MockRequest('http', 'testforum.com', ''),
                 'request': MockRequest('http', 'testforum.com', ''),
                 'url': '/t/test-thread/13/',
                 'url': '/t/test-thread/13/',
-                'pk': 13
+                'pk': 13,
             },
             },
             {
             {
                 # extract thread id from url that has port name
                 # extract thread id from url that has port name
                 'request': MockRequest('http', '127.0.0.1:8000', ''),
                 'request': MockRequest('http', '127.0.0.1:8000', ''),
                 'url': 'https://127.0.0.1:8000/t/test-thread/13/',
                 'url': 'https://127.0.0.1:8000/t/test-thread/13/',
-                'pk': 13
+                'pk': 13,
             },
             },
             {
             {
                 # extract thread id from url that isn't trimmed
                 # extract thread id from url that isn't trimmed
                 'request': MockRequest('http', '127.0.0.1:8000', ''),
                 'request': MockRequest('http', '127.0.0.1:8000', ''),
                 'url': '   /t/test-thread/13/   ',
                 'url': '   /t/test-thread/13/   ',
-                'pk': 13
+                'pk': 13,
             }
             }
         )
         )
 
 
@@ -249,27 +261,27 @@ class GetThreadIdFromUrlTests(MisagoTestCase):
             {
             {
                 # invalid wsgi alias
                 # invalid wsgi alias
                 'request': MockRequest('https', 'testforum.com'),
                 'request': MockRequest('https', 'testforum.com'),
-                'url': 'http://testforum.com/discuss/t/test-thread-123/'
+                'url': 'http://testforum.com/discuss/t/test-thread-123/',
             },
             },
             {
             {
                 # invalid hostname
                 # invalid hostname
                 'request': MockRequest('http', 'misago-project.org', '/discuss/'),
                 'request': MockRequest('http', 'misago-project.org', '/discuss/'),
-                'url': 'https://testforum.com/discuss/t/test-thread-432/post/12321/'
+                'url': 'https://testforum.com/discuss/t/test-thread-432/post/12321/',
             },
             },
             {
             {
                 # old thread url
                 # old thread url
                 'request': MockRequest('http', 'testforum.com'),
                 'request': MockRequest('http', 'testforum.com'),
-                'url': 'https://testforum.com/thread/bobboberson-123/'
+                'url': 'https://testforum.com/thread/bobboberson-123/',
             },
             },
             {
             {
                 # dashed thread url
                 # dashed thread url
                 'request': MockRequest('http', 'testforum.com'),
                 'request': MockRequest('http', 'testforum.com'),
-                'url': 'https://testforum.com/t/bobboberson-123/'
+                'url': 'https://testforum.com/t/bobboberson-123/',
             },
             },
             {
             {
                 # non-thread url
                 # non-thread url
                 'request': MockRequest('http', 'testforum.com'),
                 'request': MockRequest('http', 'testforum.com'),
-                'url': 'https://testforum.com/user/bobboberson-123/'
+                'url': 'https://testforum.com/user/bobboberson-123/',
             },
             },
             {
             {
                 # rubbish url
                 # rubbish url

+ 41 - 27
misago/threads/testutils.py

@@ -33,7 +33,7 @@ def post_thread(
         'last_post_on': started_on,
         'last_post_on': started_on,
         'is_unapproved': is_unapproved,
         'is_unapproved': is_unapproved,
         'is_hidden': is_hidden,
         'is_hidden': is_hidden,
-        'is_closed': is_closed
+        'is_closed': is_closed,
     }
     }
 
 
     if is_global:
     if is_global:
@@ -101,7 +101,10 @@ def reply_thread(
     }
     }
 
 
     try:
     try:
-        kwargs.update({'poster': poster, 'poster_name': poster.username})
+        kwargs.update({
+            'poster': poster,
+            'poster_name': poster.username,
+        })
     except AttributeError:
     except AttributeError:
         kwargs.update({'poster_name': poster})
         kwargs.update({'poster_name': poster})
 
 
@@ -127,23 +130,28 @@ def post_poll(thread, poster):
         poster_slug=poster.slug,
         poster_slug=poster.slug,
         poster_ip='127.0.0.1',
         poster_ip='127.0.0.1',
         question="Lorem ipsum dolor met?",
         question="Lorem ipsum dolor met?",
-        choices=[{
-            'hash': 'aaaaaaaaaaaa',
-            'label': 'Alpha',
-            'votes': 1
-        }, {
-            'hash': 'bbbbbbbbbbbb',
-            'label': 'Beta',
-            'votes': 0
-        }, {
-            'hash': 'gggggggggggg',
-            'label': 'Gamma',
-            'votes': 2
-        }, {
-            'hash': 'dddddddddddd',
-            'label': 'Delta',
-            'votes': 1
-        }],
+        choices=[
+            {
+                'hash': 'aaaaaaaaaaaa',
+                'label': 'Alpha',
+                'votes': 1
+            },
+            {
+                'hash': 'bbbbbbbbbbbb',
+                'label': 'Beta',
+                'votes': 0
+            },
+            {
+                'hash': 'gggggggggggg',
+                'label': 'Gamma',
+                'votes': 2
+            },
+            {
+                'hash': 'dddddddddddd',
+                'label': 'Delta',
+                'votes': 1
+            },
+        ],
         allowed_choices=2,
         allowed_choices=2,
         votes=4
         votes=4
     )
     )
@@ -161,7 +169,7 @@ def post_poll(thread, poster):
         voter_name=user.username,
         voter_name=user.username,
         voter_slug=user.slug,
         voter_slug=user.slug,
         voter_ip='127.0.0.1',
         voter_ip='127.0.0.1',
-        choice_hash='aaaaaaaaaaaa'
+        choice_hash='aaaaaaaaaaaa',
     )
     )
 
 
     # test user voted on third and last choices
     # test user voted on third and last choices
@@ -172,7 +180,7 @@ def post_poll(thread, poster):
         voter_name=poster.username,
         voter_name=poster.username,
         voter_slug=poster.slug,
         voter_slug=poster.slug,
         voter_ip='127.0.0.1',
         voter_ip='127.0.0.1',
-        choice_hash='gggggggggggg'
+        choice_hash='gggggggggggg',
     )
     )
     poll.pollvote_set.create(
     poll.pollvote_set.create(
         category=thread.category,
         category=thread.category,
@@ -181,7 +189,7 @@ def post_poll(thread, poster):
         voter_name=poster.username,
         voter_name=poster.username,
         voter_slug=poster.slug,
         voter_slug=poster.slug,
         voter_ip='127.0.0.1',
         voter_ip='127.0.0.1',
-        choice_hash='dddddddddddd'
+        choice_hash='dddddddddddd',
     )
     )
 
 
     # somebody else voted on third option before being deleted
     # somebody else voted on third option before being deleted
@@ -191,7 +199,7 @@ def post_poll(thread, poster):
         voter_name='deleted',
         voter_name='deleted',
         voter_slug='deleted',
         voter_slug='deleted',
         voter_ip='127.0.0.1',
         voter_ip='127.0.0.1',
-        choice_hash='gggggggggggg'
+        choice_hash='gggggggggggg',
     )
     )
 
 
     return poll
     return poll
@@ -208,20 +216,26 @@ def like_post(post, liker=None, username=None):
             liker=liker,
             liker=liker,
             liker_name=liker.username,
             liker_name=liker.username,
             liker_slug=liker.slug,
             liker_slug=liker.slug,
-            liker_ip='127.0.0.1'
+            liker_ip='127.0.0.1',
         )
         )
 
 
-        post.last_likes = [{'id': liker.id, 'username': liker.username}] + post.last_likes
+        post.last_likes = [{
+            'id': liker.id,
+            'username': liker.username,
+        }] + post.last_likes
     else:
     else:
         like = post.postlike_set.create(
         like = post.postlike_set.create(
             category=post.category,
             category=post.category,
             thread=post.thread,
             thread=post.thread,
             liker_name=username,
             liker_name=username,
             liker_slug=slugify(username),
             liker_slug=slugify(username),
-            liker_ip='127.0.0.1'
+            liker_ip='127.0.0.1',
         )
         )
 
 
-        post.last_likes = [{'id': None, 'username': username}] + post.last_likes
+        post.last_likes = [{
+            'id': None,
+            'username': username,
+        }] + post.last_likes
 
 
     post.likes += 1
     post.likes += 1
     post.save()
     post.save()

+ 76 - 27
misago/threads/threadtypes/privatethread.py

@@ -18,15 +18,19 @@ class PrivateThread(ThreadType):
     def get_category_last_thread_url(self, category):
     def get_category_last_thread_url(self, category):
         return reverse(
         return reverse(
             'misago:private-thread',
             'misago:private-thread',
-            kwargs={'slug': category.last_thread_slug,
-                    'pk': category.last_thread_id}
+            kwargs={
+                'slug': category.last_thread_slug,
+                'pk': category.last_thread_id,
+            }
         )
         )
 
 
     def get_category_last_post_url(self, category):
     def get_category_last_post_url(self, category):
         return reverse(
         return reverse(
             'misago:private-thread-last',
             'misago:private-thread-last',
-            kwargs={'slug': category.last_thread_slug,
-                    'pk': category.last_thread_id}
+            kwargs={
+                'slug': category.last_thread_slug,
+                'pk': category.last_thread_id,
+            }
         )
         )
 
 
     def get_category_read_api_url(self, category):
     def get_category_read_api_url(self, category):
@@ -36,70 +40,115 @@ class PrivateThread(ThreadType):
         if page > 1:
         if page > 1:
             return reverse(
             return reverse(
                 'misago:private-thread',
                 'misago:private-thread',
-                kwargs={'slug': thread.slug,
-                        'pk': thread.pk,
-                        'page': page}
+                kwargs={
+                    'slug': thread.slug,
+                    'pk': thread.pk,
+                    'page': page,
+                }
             )
             )
         else:
         else:
-            return reverse('misago:private-thread', kwargs={'slug': thread.slug, 'pk': thread.pk})
+            return reverse(
+                'misago:private-thread', kwargs={
+                    'slug': thread.slug,
+                    'pk': thread.pk,
+                }
+            )
 
 
     def get_thread_last_post_url(self, thread):
     def get_thread_last_post_url(self, thread):
-        return reverse('misago:private-thread-last', kwargs={'slug': thread.slug, 'pk': thread.pk})
+        return reverse(
+            'misago:private-thread-last', kwargs={
+                'slug': thread.slug,
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_new_post_url(self, thread):
     def get_thread_new_post_url(self, thread):
-        return reverse('misago:private-thread-new', kwargs={'slug': thread.slug, 'pk': thread.pk})
+        return reverse(
+            'misago:private-thread-new', kwargs={
+                'slug': thread.slug,
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_api_url(self, thread):
     def get_thread_api_url(self, thread):
-        return reverse('misago:api:private-thread-detail', kwargs={'pk': thread.pk})
+        return reverse(
+            'misago:api:private-thread-detail', kwargs={
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_editor_api_url(self, thread):
     def get_thread_editor_api_url(self, thread):
-        return reverse('misago:api:private-thread-post-editor', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:private-thread-post-editor', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_thread_posts_api_url(self, thread):
     def get_thread_posts_api_url(self, thread):
-        return reverse('misago:api:private-thread-post-list', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:private-thread-post-list', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_post_merge_api_url(self, thread):
     def get_post_merge_api_url(self, thread):
-        return reverse('misago:api:private-thread-post-merge', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:private-thread-post-merge', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_post_absolute_url(self, post):
     def get_post_absolute_url(self, post):
         return reverse(
         return reverse(
             'misago:private-thread-post',
             'misago:private-thread-post',
-            kwargs={'slug': post.thread.slug,
-                    'pk': post.thread.pk,
-                    'post': post.pk}
+            kwargs={
+                'slug': post.thread.slug,
+                'pk': post.thread.pk,
+                'post': post.pk,
+            }
         )
         )
 
 
     def get_post_api_url(self, post):
     def get_post_api_url(self, post):
         return reverse(
         return reverse(
             'misago:api:private-thread-post-detail',
             'misago:api:private-thread-post-detail',
-            kwargs={'thread_pk': post.thread_id,
-                    'pk': post.pk}
+            kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_likes_api_url(self, post):
     def get_post_likes_api_url(self, post):
         return reverse(
         return reverse(
             'misago:api:private-thread-post-likes',
             'misago:api:private-thread-post-likes',
-            kwargs={'thread_pk': post.thread_id,
-                    'pk': post.pk}
+            kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_editor_api_url(self, post):
     def get_post_editor_api_url(self, post):
         return reverse(
         return reverse(
             'misago:api:private-thread-post-editor',
             'misago:api:private-thread-post-editor',
-            kwargs={'thread_pk': post.thread_id,
-                    'pk': post.pk}
+            kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_edits_api_url(self, post):
     def get_post_edits_api_url(self, post):
         return reverse(
         return reverse(
             'misago:api:private-thread-post-edits',
             'misago:api:private-thread-post-edits',
-            kwargs={'thread_pk': post.thread_id,
-                    'pk': post.pk}
+            kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_read_api_url(self, post):
     def get_post_read_api_url(self, post):
         return reverse(
         return reverse(
             'misago:api:private-thread-post-read',
             'misago:api:private-thread-post-read',
-            kwargs={'thread_pk': post.thread_id,
-                    'pk': post.pk}
+            kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )

+ 111 - 37
misago/threads/threadtypes/thread.py

@@ -29,109 +29,183 @@ class Thread(ThreadType):
     def get_category_last_thread_url(self, category):
     def get_category_last_thread_url(self, category):
         return reverse(
         return reverse(
             'misago:thread',
             'misago:thread',
-            kwargs={'slug': category.last_thread_slug,
-                    'pk': category.last_thread_id}
+            kwargs={
+                'slug': category.last_thread_slug,
+                'pk': category.last_thread_id,
+            }
         )
         )
 
 
     def get_category_last_post_url(self, category):
     def get_category_last_post_url(self, category):
         return reverse(
         return reverse(
             'misago:thread-last',
             'misago:thread-last',
-            kwargs={'slug': category.last_thread_slug,
-                    'pk': category.last_thread_id}
+            kwargs={
+                'slug': category.last_thread_slug,
+                'pk': category.last_thread_id,
+            }
         )
         )
 
 
     def get_category_read_api_url(self, category):
     def get_category_read_api_url(self, category):
-        return '{}?category={}'.format(reverse('misago:api:thread-read'), category.pk)
+        return '{,}?category={,}'.format(reverse('misago:api:thread-read'), category.pk)
 
 
     def get_thread_absolute_url(self, thread, page=1):
     def get_thread_absolute_url(self, thread, page=1):
         if page > 1:
         if page > 1:
             return reverse(
             return reverse(
-                'misago:thread', kwargs={'slug': thread.slug,
-                                         'pk': thread.pk,
-                                         'page': page}
+                'misago:thread', kwargs={
+                    'slug': thread.slug,
+                    'pk': thread.pk,
+                    'page': page,
+                }
             )
             )
         else:
         else:
-            return reverse('misago:thread', kwargs={'slug': thread.slug, 'pk': thread.pk})
+            return reverse(
+                'misago:thread', kwargs={
+                    'slug': thread.slug,
+                    'pk': thread.pk,
+                }
+            )
 
 
     def get_thread_last_post_url(self, thread):
     def get_thread_last_post_url(self, thread):
-        return reverse('misago:thread-last', kwargs={'slug': thread.slug, 'pk': thread.pk})
+        return reverse(
+            'misago:thread-last', kwargs={
+                'slug': thread.slug,
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_new_post_url(self, thread):
     def get_thread_new_post_url(self, thread):
-        return reverse('misago:thread-new', kwargs={'slug': thread.slug, 'pk': thread.pk})
+        return reverse(
+            'misago:thread-new', kwargs={
+                'slug': thread.slug,
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_unapproved_post_url(self, thread):
     def get_thread_unapproved_post_url(self, thread):
-        return reverse('misago:thread-unapproved', kwargs={'slug': thread.slug, 'pk': thread.pk})
+        return reverse(
+            'misago:thread-unapproved', kwargs={
+                'slug': thread.slug,
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_api_url(self, thread):
     def get_thread_api_url(self, thread):
-        return reverse('misago:api:thread-detail', kwargs={'pk': thread.pk})
+        return reverse(
+            'misago:api:thread-detail', kwargs={
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_editor_api_url(self, thread):
     def get_thread_editor_api_url(self, thread):
-        return reverse('misago:api:thread-post-editor', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-post-editor', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_thread_merge_api_url(self, thread):
     def get_thread_merge_api_url(self, thread):
-        return reverse('misago:api:thread-merge', kwargs={'pk': thread.pk})
+        return reverse(
+            'misago:api:thread-merge', kwargs={
+                'pk': thread.pk,
+            }
+        )
 
 
     def get_thread_poll_api_url(self, thread):
     def get_thread_poll_api_url(self, thread):
-        return reverse('misago:api:thread-poll-list', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-poll-list', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_thread_posts_api_url(self, thread):
     def get_thread_posts_api_url(self, thread):
-        return reverse('misago:api:thread-post-list', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-post-list', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_poll_api_url(self, poll):
     def get_poll_api_url(self, poll):
         return reverse(
         return reverse(
-            'misago:api:thread-poll-detail', kwargs={'thread_pk': poll.thread_id,
-                                                     'pk': poll.pk}
+            'misago:api:thread-poll-detail', kwargs={
+                'thread_pk': poll.thread_id,
+                'pk': poll.pk,
+            }
         )
         )
 
 
     def get_poll_votes_api_url(self, poll):
     def get_poll_votes_api_url(self, poll):
         return reverse(
         return reverse(
-            'misago:api:thread-poll-votes', kwargs={'thread_pk': poll.thread_id,
-                                                    'pk': poll.pk}
+            'misago:api:thread-poll-votes', kwargs={
+                'thread_pk': poll.thread_id,
+                'pk': poll.pk,
+            }
         )
         )
 
 
     def get_post_merge_api_url(self, thread):
     def get_post_merge_api_url(self, thread):
-        return reverse('misago:api:thread-post-merge', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-post-merge', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_post_move_api_url(self, thread):
     def get_post_move_api_url(self, thread):
-        return reverse('misago:api:thread-post-move', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-post-move', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_post_split_api_url(self, thread):
     def get_post_split_api_url(self, thread):
-        return reverse('misago:api:thread-post-split', kwargs={'thread_pk': thread.pk})
+        return reverse(
+            'misago:api:thread-post-split', kwargs={
+                'thread_pk': thread.pk,
+            }
+        )
 
 
     def get_post_absolute_url(self, post):
     def get_post_absolute_url(self, post):
         return reverse(
         return reverse(
             'misago:thread-post',
             'misago:thread-post',
-            kwargs={'slug': post.thread.slug,
-                    'pk': post.thread.pk,
-                    'post': post.pk}
+            kwargs={
+                'slug': post.thread.slug,
+                'pk': post.thread.pk,
+                'post': post.pk,
+            }
         )
         )
 
 
     def get_post_api_url(self, post):
     def get_post_api_url(self, post):
         return reverse(
         return reverse(
-            'misago:api:thread-post-detail', kwargs={'thread_pk': post.thread_id,
-                                                     'pk': post.pk}
+            'misago:api:thread-post-detail', kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_likes_api_url(self, post):
     def get_post_likes_api_url(self, post):
         return reverse(
         return reverse(
-            'misago:api:thread-post-likes', kwargs={'thread_pk': post.thread_id,
-                                                    'pk': post.pk}
+            'misago:api:thread-post-likes', kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_editor_api_url(self, post):
     def get_post_editor_api_url(self, post):
         return reverse(
         return reverse(
-            'misago:api:thread-post-editor', kwargs={'thread_pk': post.thread_id,
-                                                     'pk': post.pk}
+            'misago:api:thread-post-editor', kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_edits_api_url(self, post):
     def get_post_edits_api_url(self, post):
         return reverse(
         return reverse(
-            'misago:api:thread-post-edits', kwargs={'thread_pk': post.thread_id,
-                                                    'pk': post.pk}
+            'misago:api:thread-post-edits', kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )
 
 
     def get_post_read_api_url(self, post):
     def get_post_read_api_url(self, post):
         return reverse(
         return reverse(
-            'misago:api:thread-post-read', kwargs={'thread_pk': post.thread_id,
-                                                   'pk': post.pk}
+            'misago:api:thread-post-read', kwargs={
+                'thread_pk': post.thread_id,
+                'pk': post.pk,
+            }
         )
         )

+ 2 - 3
misago/threads/utils.py

@@ -31,9 +31,8 @@ def add_categories_to_items(root_category, categories, items):
         else:
         else:
             # item from other category's scope
             # item from other category's scope
             for category in categories:
             for category in categories:
-                if category.level == 1 and (
-                        category == item.category or category.has_child(item.category)
-                ):
+                category_is_parent = category.has_child(item.category)
+                if category.level == 1 and (category == item.category or category_is_parent):
                     top_categories_map[item.category_id] = category
                     top_categories_map[item.category_id] = category
                     item.top_category = category
                     item.top_category = category
 
 

+ 20 - 12
misago/threads/validators.py

@@ -43,22 +43,26 @@ def validate_post(post):
         message = ungettext(
         message = ungettext(
             "Posted message should be at least %(limit_value)s character long (it has %(show_value)s).",
             "Posted message should be at least %(limit_value)s character long (it has %(show_value)s).",
             "Posted message should be at least %(limit_value)s characters long (it has %(show_value)s).",
             "Posted message should be at least %(limit_value)s characters long (it has %(show_value)s).",
-            settings.post_length_min
+            settings.post_length_min,
         )
         )
         raise ValidationError(
         raise ValidationError(
-            message % {'limit_value': settings.post_length_min,
-                       'show_value': post_len}
+            message % {
+                'limit_value': settings.post_length_min,
+                'show_value': post_len,
+            }
         )
         )
 
 
     if settings.post_length_max and post_len > settings.post_length_max:
     if settings.post_length_max and post_len > settings.post_length_max:
         message = ungettext(
         message = ungettext(
             "Posted message cannot be longer than %(limit_value)s character (it has %(show_value)s).",
             "Posted message cannot be longer than %(limit_value)s character (it has %(show_value)s).",
             "Posted message cannot be longer than %(limit_value)s characters (it has %(show_value)s).",
             "Posted message cannot be longer than %(limit_value)s characters (it has %(show_value)s).",
-            settings.post_length_max
+            settings.post_length_max,
         )
         )
         raise ValidationError(
         raise ValidationError(
-            message % {'limit_value': settings.post_length_max,
-                       'show_value': post_len}
+            message % {
+                'limit_value': settings.post_length_max,
+                'show_value': post_len,
+            }
         )
         )
 
 
 
 
@@ -72,22 +76,26 @@ def validate_title(title):
         message = ungettext(
         message = ungettext(
             "Thread title should be at least %(limit_value)s character long (it has %(show_value)s).",
             "Thread title should be at least %(limit_value)s character long (it has %(show_value)s).",
             "Thread title should be at least %(limit_value)s characters long (it has %(show_value)s).",
             "Thread title should be at least %(limit_value)s characters long (it has %(show_value)s).",
-            settings.thread_title_length_min
+            settings.thread_title_length_min,
         )
         )
         raise ValidationError(
         raise ValidationError(
-            message % {'limit_value': settings.thread_title_length_min,
-                       'show_value': title_len}
+            message % {
+                'limit_value': settings.thread_title_length_min,
+                'show_value': title_len,
+            }
         )
         )
 
 
     if title_len > settings.thread_title_length_max:
     if title_len > settings.thread_title_length_max:
         message = ungettext(
         message = ungettext(
             "Thread title cannot be longer than %(limit_value)s character (it has %(show_value)s).",
             "Thread title cannot be longer than %(limit_value)s character (it has %(show_value)s).",
             "Thread title cannot be longer than %(limit_value)s characters (it has %(show_value)s).",
             "Thread title cannot be longer than %(limit_value)s characters (it has %(show_value)s).",
-            settings.thread_title_length_max
+            settings.thread_title_length_max,
         )
         )
         raise ValidationError(
         raise ValidationError(
-            message % {'limit_value': settings.thread_title_length_max,
-                       'show_value': title_len}
+            message % {
+                'limit_value': settings.thread_title_length_max,
+                'show_value': title_len,
+            }
         )
         )
 
 
     error_not_sluggable = _("Thread title should contain alpha-numeric characters.")
     error_not_sluggable = _("Thread title should contain alpha-numeric characters.")

+ 13 - 3
misago/threads/viewmodels/category.py

@@ -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['browseable_categories']
+                id__in=request.user.acl_cache['browseable_categories'],
             ).select_related('parent')
             ).select_related('parent')
         )
         )
 
 
@@ -87,6 +87,16 @@ class PrivateThreadsCategory(ViewModel):
 
 
 
 
 BasicCategorySerializer = CategorySerializer.subset_fields(
 BasicCategorySerializer = CategorySerializer.subset_fields(
-    'id', 'parent', 'name', 'description', 'is_closed', 'css_class', 'absolute_url', 'api_url',
-    'level', 'lft', 'rght', 'is_read'
+    'id',
+    'parent',
+    'name',
+    'description',
+    'is_closed',
+    'css_class',
+    'absolute_url',
+    'api_url',
+    'level',
+    'lft',
+    'rght',
+    'is_read',
 )
 )

+ 8 - 2
misago/threads/viewmodels/posts.py

@@ -71,7 +71,10 @@ class ViewModel(object):
 
 
     def get_posts_queryset(self, request, thread):
     def get_posts_queryset(self, request, thread):
         queryset = thread.post_set.select_related(
         queryset = thread.post_set.select_related(
-            'poster', 'poster__rank', 'poster__ban_cache', 'poster__online_tracker'
+            'poster',
+            'poster__rank',
+            'poster__ban_cache',
+            'poster__online_tracker',
         ).filter(is_event=False).order_by('id')
         ).filter(is_event=False).order_by('id')
         return exclude_invisible_posts(request.user, thread.category, queryset)
         return exclude_invisible_posts(request.user, thread.category, queryset)
 
 
@@ -96,7 +99,10 @@ class ViewModel(object):
         return context
         return context
 
 
     def get_template_context(self):
     def get_template_context(self):
-        return {'posts': self.posts, 'paginator': self.paginator}
+        return {
+            'posts': self.posts,
+            'paginator': self.paginator,
+        }
 
 
 
 
 class ThreadPosts(ViewModel):
 class ThreadPosts(ViewModel):

+ 5 - 3
misago/threads/viewmodels/thread.py

@@ -91,7 +91,7 @@ class ViewModel(BaseViewModel):
             'thread': self._model,
             'thread': self._model,
             'poll': self._poll,
             'poll': self._poll,
             'category': self._model.category,
             'category': self._model.category,
-            'breadcrumbs': self._model.path
+            'breadcrumbs': self._model.path,
         }
         }
 
 
 
 
@@ -103,7 +103,9 @@ class ForumThread(ViewModel):
             queryset = Thread.objects.select_related(*BASE_RELATIONS)
             queryset = Thread.objects.select_related(*BASE_RELATIONS)
 
 
         thread = get_object_or_404(
         thread = get_object_or_404(
-            queryset, pk=pk, category__tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
+            queryset,
+            pk=pk,
+            category__tree_id=trees_map.get_tree_id_for_root(THREADS_ROOT_NAME),
         )
         )
 
 
         allow_see_thread(request.user, thread)
         allow_see_thread(request.user, thread)
@@ -130,7 +132,7 @@ class PrivateThread(ViewModel):
         thread = get_object_or_404(
         thread = get_object_or_404(
             queryset,
             queryset,
             pk=pk,
             pk=pk,
-            category__tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME)
+            category__tree_id=trees_map.get_tree_id_for_root(PRIVATE_THREADS_ROOT_NAME),
         )
         )
 
 
         make_participants_aware(request.user, thread)
         make_participants_aware(request.user, thread)

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

@@ -123,7 +123,7 @@ class ViewModel(object):
         context = {
         context = {
             'THREADS': {
             'THREADS': {
                 'results': ThreadsListSerializer(self.threads, many=True).data,
                 'results': ThreadsListSerializer(self.threads, many=True).data,
-                'subcategories': [c.pk for c in self.category.children]
+                'subcategories': [c.pk for c in self.category.children],
             },
             },
         }
         }
 
 
@@ -135,7 +135,7 @@ class ViewModel(object):
             'list_name': self.get_list_name(self.list_type),
             'list_name': self.get_list_name(self.list_type),
             'list_type': self.list_type,
             'list_type': self.list_type,
             'threads': self.threads,
             'threads': self.threads,
-            'paginator': self.paginator
+            'paginator': self.paginator,
         }
         }
 
 
 
 
@@ -244,7 +244,7 @@ def filter_read_threads_queryset(user, categories, list_type, queryset):
         read_threads = user.threadread_set.filter(
         read_threads = user.threadread_set.filter(
             category__in=categories,
             category__in=categories,
             thread__last_post_on__gt=cutoff_date,
             thread__last_post_on__gt=cutoff_date,
-            last_read_on__lt=F('thread__last_post_on')
+            last_read_on__lt=F('thread__last_post_on'),
         ).values('thread_id')
         ).values('thread_id')
 
 
         queryset = queryset.filter(id__in=read_threads)
         queryset = queryset.filter(id__in=read_threads)

+ 9 - 7
misago/threads/views/admin/attachments.py

@@ -25,13 +25,15 @@ class AttachmentsList(AttachmentAdmin, generic.ListView):
                 ('-size', _("Largest files")), )
                 ('-size', _("Largest files")), )
     selection_label = _('With attachments: 0')
     selection_label = _('With attachments: 0')
     empty_selection_label = _('Select attachments')
     empty_selection_label = _('Select attachments')
-    mass_actions = [{
-        'action': 'delete',
-        'name': _("Delete attachments"),
-        'icon': 'fa fa-times-circle',
-        'confirmation': _("Are you sure you want to delete selected attachments?"),
-        'is_atomic': False
-    }]
+    mass_actions = [
+        {
+            'action': 'delete',
+            'name': _("Delete attachments"),
+            'icon': 'fa fa-times-circle',
+            'confirmation': _("Are you sure you want to delete selected attachments?"),
+            'is_atomic': False,
+        },
+    ]
 
 
     def get_search_form(self, request):
     def get_search_form(self, request):
         return SearchAttachmentsForm
         return SearchAttachmentsForm

+ 11 - 4
misago/threads/views/goto.py

@@ -89,7 +89,9 @@ class ThreadGotoNewView(GotoView):
 
 
     def get_target_post(self, thread, posts_queryset, **kwargs):
     def get_target_post(self, thread, posts_queryset, **kwargs):
         if thread.is_new:
         if thread.is_new:
-            return posts_queryset.filter(posted_on__gt=thread.last_read_on).order_by('id').first()
+            return posts_queryset.filter(
+                posted_on__gt=thread.last_read_on,
+            ).order_by('id').first()
         else:
         else:
             return posts_queryset.order_by('id').last()
             return posts_queryset.order_by('id').last()
 
 
@@ -101,12 +103,15 @@ class ThreadGotoUnapprovedView(GotoView):
         if not thread.acl['can_approve']:
         if not thread.acl['can_approve']:
             raise PermissionDenied(
             raise PermissionDenied(
                 _(
                 _(
-                    "You need permission to approve content to be able to go to first unapproved post."
+                    "You need permission to approve content to "
+                    "be able to go to first unapproved post."
                 )
                 )
             )
             )
 
 
     def get_target_post(self, thread, posts_queryset, **kwargs):
     def get_target_post(self, thread, posts_queryset, **kwargs):
-        unapproved_post = posts_queryset.filter(is_unapproved=True).order_by('id').first()
+        unapproved_post = posts_queryset.filter(
+            is_unapproved=True,
+        ).order_by('id').first()
         if unapproved_post:
         if unapproved_post:
             return unapproved_post
             return unapproved_post
         else:
         else:
@@ -133,6 +138,8 @@ class PrivateThreadGotoNewView(GotoView):
 
 
     def get_target_post(self, thread, posts_queryset, **kwargs):
     def get_target_post(self, thread, posts_queryset, **kwargs):
         if thread.is_new:
         if thread.is_new:
-            return posts_queryset.filter(posted_on__gt=thread.last_read_on).order_by('id').first()
+            return posts_queryset.filter(
+                posted_on__gt=thread.last_read_on,
+            ).order_by('id').first()
         else:
         else:
             return posts_queryset.order_by('id').last()
             return posts_queryset.order_by('id').last()

+ 6 - 2
misago/threads/views/thread.py

@@ -45,7 +45,9 @@ class ThreadBase(View):
     def get_template_context(self, request, thread, posts):
     def get_template_context(self, request, thread, posts):
         context = {
         context = {
             'url_name':
             'url_name':
-                ':'.join(request.resolver_match.namespaces + [request.resolver_match.url_name])
+                ':'.join(request.resolver_match.namespaces + [
+                    request.resolver_match.url_name,
+                ])
         }
         }
 
 
         context.update(thread.get_template_context())
         context.update(thread.get_template_context())
@@ -59,7 +61,9 @@ class ThreadView(ThreadBase):
     template_name = 'misago/thread/thread.html'
     template_name = 'misago/thread/thread.html'
 
 
     def get_default_frontend_context(self):
     def get_default_frontend_context(self):
-        return {'THREADS_API': reverse('misago:api:thread-list')}
+        return {
+            'THREADS_API': reverse('misago:api:thread-list'),
+        }
 
 
 
 
 class PrivateThreadView(ThreadBase):
 class PrivateThreadView(ThreadBase):