Rafał Pitoń 8 лет назад
Родитель
Сommit
f9b0b2c957
73 измененных файлов с 333 добавлено и 178 удалено
  1. 3 2
      .style.yapf
  2. 1 2
      misago/acl/algebra.py
  3. 8 6
      misago/acl/views.py
  4. 1 2
      misago/admin/hierarchy.py
  5. 31 16
      misago/admin/views/index.py
  6. 1 1
      misago/categories/forms.py
  7. 3 1
      misago/categories/migrations/0003_categories_roles.py
  8. 5 1
      misago/categories/models.py
  9. 2 2
      misago/categories/serializers.py
  10. 2 1
      misago/categories/tests/test_categories_admin_views.py
  11. 3 1
      misago/categories/tests/test_permissions_admin_views.py
  12. 3 1
      misago/categories/tests/test_utils.py
  13. 8 6
      misago/categories/views/permsadmin.py
  14. 2 4
      misago/conf/forms.py
  15. 3 1
      misago/conf/tests/test_migrationutils.py
  16. 3 1
      misago/core/exceptionhandler.py
  17. 1 1
      misago/core/page.py
  18. 1 1
      misago/core/pgutils.py
  19. 3 1
      misago/core/testproject/views.py
  20. 2 2
      misago/core/tests/test_exceptionhandlers.py
  21. 5 3
      misago/core/tests/test_utils.py
  22. 1 1
      misago/datamover/management/commands/moveusers.py
  23. 18 7
      misago/datamover/urls.py
  24. 3 1
      misago/legal/tests.py
  25. 3 1
      misago/readtracker/categoriestracker.py
  26. 1 3
      misago/threads/api/postendpoints/merge.py
  27. 3 1
      misago/threads/api/postingendpoint/__init__.py
  28. 3 1
      misago/threads/api/postingendpoint/attachments.py
  29. 1 2
      misago/threads/api/postingendpoint/floodprotection.py
  30. 3 2
      misago/threads/migrations/0001_initial.py
  31. 2 1
      misago/threads/paginator.py
  32. 3 1
      misago/threads/participants.py
  33. 3 1
      misago/threads/permissions/privatethreads.py
  34. 7 3
      misago/threads/permissions/threads.py
  35. 3 1
      misago/threads/serializers/moderation.py
  36. 6 2
      misago/threads/signals.py
  37. 6 6
      misago/threads/tests/test_attachments_middleware.py
  38. 3 1
      misago/threads/tests/test_emailnotification_middleware.py
  39. 17 11
      misago/threads/tests/test_gotoviews.py
  40. 2 1
      misago/threads/tests/test_post_mentions.py
  41. 3 1
      misago/threads/tests/test_subscription_middleware.py
  42. 3 1
      misago/threads/tests/test_thread_editreply_api.py
  43. 3 1
      misago/threads/tests/test_thread_poll_api.py
  44. 4 2
      misago/threads/tests/test_thread_pollvotes_api.py
  45. 4 2
      misago/threads/tests/test_thread_postmerge_api.py
  46. 3 1
      misago/threads/tests/test_thread_postmove_api.py
  47. 38 6
      misago/threads/tests/test_thread_postpatch_api.py
  48. 3 2
      misago/threads/tests/test_thread_postread_api.py
  49. 4 2
      misago/threads/tests/test_thread_postsplit_api.py
  50. 6 2
      misago/threads/tests/test_thread_reply_api.py
  51. 6 2
      misago/threads/tests/test_threads_editor_api.py
  52. 6 2
      misago/threads/tests/test_threads_merge_api.py
  53. 1 2
      misago/threads/tests/test_threadslists.py
  54. 1 1
      misago/threads/tests/test_threadview.py
  55. 2 1
      misago/threads/tests/test_treesmap.py
  56. 2 1
      misago/threads/urls/__init__.py
  57. 3 1
      misago/threads/urls/api.py
  58. 3 2
      misago/threads/viewmodels/category.py
  59. 4 4
      misago/threads/viewmodels/threads.py
  60. 3 1
      misago/users/api/userendpoints/changepassword.py
  61. 1 1
      misago/users/avatars/uploaded.py
  62. 3 1
      misago/users/forms/auth.py
  63. 7 2
      misago/users/management/commands/createsuperuser.py
  64. 3 1
      misago/users/models/user.py
  65. 2 3
      misago/users/permissions/moderation.py
  66. 1 2
      misago/users/search.py
  67. 2 3
      misago/users/serializers/auth.py
  68. 3 1
      misago/users/tests/test_auth_api.py
  69. 19 6
      misago/users/tests/test_user_avatar_api.py
  70. 3 1
      misago/users/tests/test_useradmin_views.py
  71. 2 4
      misago/users/validators.py
  72. 2 4
      misago/users/views/activation.py
  73. 4 9
      misago/users/views/forgottenpassword.py

+ 3 - 2
.style.yapf

@@ -1,6 +1,6 @@
 [style]
 coalesce_brackets = true
-column_limit=100
+column_limit=99
 dedent_closing_brackets = true
 each_dict_entry_on_separate_line = true
 indent_dictionary_value = true
@@ -9,4 +9,5 @@ split_arguments_when_comma_terminated = true
 split_before_first_argument = true
 split_before_logical_operator = true
 split_before_named_assigns = true
-split_penalty_import_names = 2
+split_penalty_after_opening_bracket = 200
+split_penalty_excess_character = 200

+ 1 - 2
misago/acl/algebra.py

@@ -17,8 +17,7 @@ def sum_acls(result_acl, acls=None, roles=None, key=None, **permissions):
     if roles is not None:
         if not key:
             raise ValueError(
-                'You have to provide "key" argument if '
-                'you are passing roles instead of acls'
+                'You have to provide "key" argument if you are passing roles instead of acls'
             )
         acls = _roles_acls(key, roles)
 

+ 8 - 6
misago/acl/views.py

@@ -52,11 +52,13 @@ class RoleFormMixin(object):
             elif form.is_valid() and len(perms_forms) != valid_forms:
                 form.add_error(None, _("Form contains errors."))
 
-        return self.render(request, {
-            'form': form,
-            'target': target,
-            'perms_forms': perms_forms,
-        })
+        return self.render(
+            request, {
+                'form': form,
+                'target': target,
+                'perms_forms': perms_forms,
+            }
+        )
 
 
 class NewRole(RoleFormMixin, RoleAdmin, generic.ModelFormView):
@@ -70,7 +72,7 @@ class EditRole(RoleFormMixin, RoleAdmin, generic.ModelFormView):
 class DeleteRole(RoleAdmin, generic.ButtonView):
     def check_permissions(self, request, target):
         if target.special_role:
-            message = _('Role "%(name)s" is special role ' 'and can\'t be deleted.')
+            message = _('Role "%(name)s" is special role and can\'t be deleted.')
             return message % {'name': target.name}
 
     def button_action(self, request, target):

+ 1 - 2
misago/admin/hierarchy.py

@@ -100,8 +100,7 @@ class AdminHierarchyBuilder(object):
             iterations += 1
             if iterations > 512:
                 message = (
-                    "Misago Admin hierarchy is invalid or too complex "
-                    "to resolve. Nodes left: %s"
+                    "Misago Admin hierarchy is invalid or too complex to resolve. Nodes left: %s"
                 )
                 raise ValueError(message % self.nodes_record)
 

+ 31 - 16
misago/admin/views/index.py

@@ -20,28 +20,31 @@ UserModel = get_user_model()
 
 
 def admin_index(request):
+    inactive_users_queryset = UserModel.objects.exclude(
+        requires_activation=UserModel.ACTIVATION_NONE,
+    )
+
     db_stats = {
-        'threads':
-            Thread.objects.count(),
-        'posts':
-            Post.objects.count(),
-        'users':
-            UserModel.objects.count(),
-        'inactive_users':
-            UserModel.objects.exclude(requires_activation=UserModel.ACTIVATION_NONE).count()
+        'threads': Thread.objects.count(),
+        'posts': Post.objects.count(),
+        'users': UserModel.objects.count(),
+        'inactive_users': inactive_users_queryset.count()
     }
 
     return render(
-        request, 'misago/admin/index.html',
-        {'db_stats': db_stats,
-         'version_check': cache.get(VERSION_CHECK_CACHE_KEY)}
+        request, 'misago/admin/index.html', {
+            'db_stats': db_stats,
+            'version_check': cache.get(VERSION_CHECK_CACHE_KEY),
+        }
     )
 
 
 def check_version(request):
     if request.method != "POST":
         raise Http404()
+
     version = cache.get(VERSION_CHECK_CACHE_KEY, 'nada')
+
     if version == 'nada':
         try:
             api_url = 'https://api.github.com/repos/rafalp/Misago/releases'
@@ -57,12 +60,20 @@ def check_version(request):
             for i in range(3):
                 if latest[i] > current[i]:
                     message = _("Outdated: %(current)s < %(latest)s")
-                    formats = {'latest': latest_version, 'current': __version__}
-
-                    version = {'is_error': True, 'message': message % formats}
+                    formats = {
+                        'latest': latest_version,
+                        'current': __version__,
+                    }
+
+                    version = {
+                        'is_error': True,
+                        'message': message % formats,
+                    }
                     break
             else:
-                formats = {'current': __version__}
+                formats = {
+                    'current': __version__,
+                }
                 version = {
                     'is_error': False,
                     'message': _("Up to date! (%(current)s)") % formats,
@@ -71,5 +82,9 @@ def check_version(request):
             cache.set(VERSION_CHECK_CACHE_KEY, version, 180)
         except (RequestException, IndexError, KeyError, ValueError):
             message = _("Failed to connect to GitHub API. Try again later.")
-            version = {'is_error': True, 'message': message}
+            version = {
+                'is_error': True,
+                'message': message,
+            }
+
     return JsonResponse(version)

+ 1 - 1
misago/categories/forms.py

@@ -187,7 +187,7 @@ class DeleteCategoryFormBase(forms.ModelForm):
 
         if data.get('move_threads_to'):
             if data['move_threads_to'].pk == self.instance.pk:
-                message = _("You are trying to move this category threads to " "itself.")
+                message = _("You are trying to move this category threads to itself.")
                 raise forms.ValidationError(message)
 
             moving_to_child = self.instance.has_child(data['move_threads_to'])

+ 3 - 1
misago/categories/migrations/0003_categories_roles.py

@@ -172,7 +172,9 @@ def create_default_categories_roles(apps, schema_editor):
     )
 
     RoleCategoryACL.objects.create(
-        role=Role.objects.get(special_role='anonymous'), category=category, category_role=read_only
+        role=Role.objects.get(special_role='anonymous'),
+        category=category,
+        category_role=read_only
     )
 
 

+ 5 - 1
misago/categories/models.py

@@ -70,7 +70,11 @@ class Category(MPTTModel):
     posts = models.PositiveIntegerField(default=0)
     last_post_on = models.DateTimeField(null=True, blank=True)
     last_thread = models.ForeignKey(
-        'misago_threads.Thread', related_name='+', null=True, blank=True, on_delete=models.SET_NULL
+        'misago_threads.Thread',
+        related_name='+',
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL
     )
     last_thread_title = models.CharField(max_length=255, null=True, blank=True)
     last_thread_slug = models.CharField(max_length=255, null=True, blank=True)

+ 2 - 2
misago/categories/serializers.py

@@ -44,8 +44,8 @@ class CategorySerializer(serializers.ModelSerializer, MutableFields):
         fields = (
             'id', 'parent', 'name', 'description', 'is_closed', 'threads', 'posts', 'last_post_on',
             'last_thread_title', 'last_poster_name', 'css_class', 'is_read', 'subcategories',
-            'absolute_url', 'last_thread_url', 'last_post_url', 'last_poster_url', 'acl', 'api_url',
-            'level', 'lft', 'rght',
+            'absolute_url', 'last_thread_url', 'last_post_url', 'last_poster_url', 'acl',
+            'api_url', 'level', 'lft', 'rght',
         )
 
     def get_description(self, obj):

+ 2 - 1
misago/categories/tests/test_categories_admin_views.py

@@ -14,7 +14,8 @@ class CategoryAdminTestCate(AdminTestCase):
         current_tree = []
         for category in queryset:
             current_tree.append((
-                category, category.level, category.lft - root.lft + 1, category.rght - root.lft + 1,
+                category, category.level, category.lft - root.lft + 1,
+                category.rght - root.lft + 1,
             ))
 
         if len(expected_tree) != len(current_tree):

+ 3 - 1
misago/categories/tests/test_permissions_admin_views.py

@@ -169,7 +169,9 @@ class CategoryRoleAdminViewsTests(AdminTestCase):
         # Check that roles were assigned
         category_role_set = test_category.category_role_set
         self.assertEqual(category_role_set.get(role=test_role_a).category_role_id, role_full.pk)
-        self.assertEqual(category_role_set.get(role=test_role_b).category_role_id, role_comments.pk)
+        self.assertEqual(
+            category_role_set.get(role=test_role_b).category_role_id, role_comments.pk
+        )
 
     def test_change_role_categories_permissions_view(self):
         """change role categories perms view works"""

+ 3 - 1
misago/categories/tests/test_utils.py

@@ -93,7 +93,9 @@ class CategoriesUtilsTests(AuthenticatedUserTestCase):
 
     def test_root_categories_tree_with_leaf(self):
         """get_categories_tree returns all children of given node"""
-        categories_tree = get_categories_tree(self.user, Category.objects.get(slug='subcategory-f'))
+        categories_tree = get_categories_tree(
+            self.user, Category.objects.get(slug='subcategory-f')
+        )
         self.assertEqual(len(categories_tree), 0)
 
     def test_get_category_path(self):

+ 8 - 6
misago/categories/views/permsadmin.py

@@ -57,11 +57,13 @@ class RoleFormMixin(object):
             elif form.is_valid() and len(perms_forms) != valid_forms:
                 form.add_error(None, _("Form contains errors."))
 
-        return self.render(request, {
-            'form': form,
-            'target': target,
-            'perms_forms': perms_forms,
-        })
+        return self.render(
+            request, {
+                'form': form,
+                'target': target,
+                'perms_forms': perms_forms,
+            }
+        )
 
 
 class NewCategoryRole(RoleFormMixin, CategoryRoleAdmin, generic.ModelFormView):
@@ -75,7 +77,7 @@ class EditCategoryRole(RoleFormMixin, CategoryRoleAdmin, generic.ModelFormView):
 class DeleteCategoryRole(CategoryRoleAdmin, generic.ButtonView):
     def check_permissions(self, request, target):
         if target.special_role:
-            message = _('Role "%(name)s" is special ' 'role and can\'t be deleted.')
+            message = _('Role "%(name)s" is special role and can\'t be deleted.')
             return message % {'name': target.name}
 
     def button_action(self, request, target):

+ 2 - 4
misago/conf/forms.py

@@ -21,16 +21,14 @@ class ValidateChoicesNum(object):
                 'You have to select at least %(choices)d option.',
                 'You have to select at least %(choices)d options.', self.min_choices
             )
-            message = message % {'choices': self.min_choices}
-            raise forms.ValidationError(message)
+            raise forms.ValidationError(message % {'choices': self.min_choices})
 
         if self.max_choices and self.max_choices < data_len:
             message = ungettext(
                 'You cannot select more than %(choices)d option.',
                 'You cannot select more than %(choices)d options.', self.max_choices
             )
-            message = message % {'choices': self.max_choices}
-            raise forms.ValidationError(message)
+            raise forms.ValidationError(message % {'choices': self.max_choices})
 
         return data
 

+ 3 - 1
misago/conf/tests/test_migrationutils.py

@@ -82,7 +82,9 @@ class DBConfMigrationUtilsTests(TestCase):
             }, )
         }
 
-        migrationutils.migrate_settings_group(apps, new_group, old_group_key=self.test_group['key'])
+        migrationutils.migrate_settings_group(
+            apps, new_group, old_group_key=self.test_group['key']
+        )
         db_group = migrationutils.get_group(
             apps.get_model('misago_conf', 'SettingsGroup'), new_group['key']
         )

+ 3 - 1
misago/core/exceptionhandler.py

@@ -9,7 +9,9 @@ from . import errorpages
 from .exceptions import AjaxError, Banned, ExplicitFirstPage, OutdatedSlug
 
 
-HANDLED_EXCEPTIONS = (AjaxError, Banned, ExplicitFirstPage, Http404, OutdatedSlug, PermissionDenied)
+HANDLED_EXCEPTIONS = (
+    AjaxError, Banned, ExplicitFirstPage, Http404, OutdatedSlug, PermissionDenied
+)
 
 
 def is_misago_exception(exception):

+ 1 - 1
misago/core/page.py

@@ -71,7 +71,7 @@ class Page(object):
             self, link, after=None, before=None, visible_if=None, get_metadata=None, **kwargs
     ):
         if self._finalized:
-            message = ("%s page was initialized already and no longer " "accepts new sections")
+            message = ("%s page was initialized already and no longer accepts new sections")
             raise RuntimeError(message % self.name)
 
         if after and before:

+ 1 - 1
misago/core/pgutils.py

@@ -95,6 +95,6 @@ DROP INDEX %(index_name)s
         schema_editor.execute(statement)
 
     def describe(self):
-        message = ("Create PostgreSQL partial composite " "index on fields %s in %s for %s")
+        message = ("Create PostgreSQL partial composite index on fields %s in %s for %s")
         formats = (', '.join(self.fields), self.model_name, self.values)
         return message % formats

+ 3 - 1
misago/core/testproject/views.py

@@ -26,7 +26,9 @@ def test_mail_user(request):
 
 
 def test_mail_users(request):
-    mail.mail_users(request, UserModel.objects.iterator(), "Misago Test Spam", "misago/emails/base")
+    mail.mail_users(
+        request, UserModel.objects.iterator(), "Misago Test Spam", "misago/emails/base"
+    )
 
     return HttpResponse("Mailed users!")
 

+ 2 - 2
misago/core/tests/test_exceptionhandlers.py

@@ -9,8 +9,8 @@ from misago.users.models import Ban
 
 
 INVALID_EXCEPTIONS = (
-    django_exceptions.ObjectDoesNotExist, django_exceptions.ViewDoesNotExist, TypeError, ValueError,
-    KeyError,
+    django_exceptions.ObjectDoesNotExist, django_exceptions.ViewDoesNotExist, TypeError,
+    ValueError, KeyError,
 )
 
 

+ 5 - 3
misago/core/tests/test_utils.py

@@ -35,7 +35,8 @@ class IsRequestToMisagoTests(TestCase):
             request = RequestFactory().get('/')
             request.path_info = path
             self.assertFalse(
-                is_request_to_misago(request), '"%s" is overlapped by "%s"' % (path, misago_prefix)
+                is_request_to_misago(request),
+                '"%s" is overlapped by "%s"' % (path, misago_prefix)
             )
 
 
@@ -220,8 +221,9 @@ class IsRefererLocalTests(TestCase):
     def test_foreign_referers(self):
         """non-local referers return false"""
         bad_request = MockRequest(
-            'GET', {'HTTP_REFERER': 'http://else-project.org/',
-                    'HTTP_HOST': 'misago-project.org/'}
+            'GET',
+            {'HTTP_REFERER': 'http://else-project.org/',
+             'HTTP_HOST': 'misago-project.org/'}
         )
         self.assertFalse(is_referer_local(bad_request))
 

+ 1 - 1
misago/datamover/management/commands/moveusers.py

@@ -3,7 +3,7 @@ from misago.datamover.management.base import BaseCommand
 
 
 class Command(BaseCommand):
-    help = ("Moves users, avatars, followers, blocks " "and bans from Misago 0.5")
+    help = ("Moves users, avatars, followers, blocks and bans from Misago 0.5")
 
     def handle(self, *args, **options):
         self.stdout.write("Moving users from Misago 0.5:")

+ 18 - 7
misago/datamover/urls.py

@@ -43,11 +43,16 @@ urlpatterns += [
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', views.thread_redirect),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', views.thread_redirect),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', views.thread_redirect),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', views.thread_redirect),
     url(
-        r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', views.thread_redirect
+        r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', views.thread_redirect
+    ),
+    url(
+        r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$',
+        views.thread_redirect
+    ),
+    url(
+        r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', views.thread_redirect
     ),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/report/$', views.thread_redirect),
     url(
         r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show-report/$',
         views.thread_redirect
@@ -55,7 +60,9 @@ urlpatterns += [
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', views.thread_redirect),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', views.thread_redirect),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show/$', views.thread_redirect),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', views.thread_redirect),
+    url(
+        r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', views.thread_redirect
+    ),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', views.thread_redirect),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/show/$', views.thread_redirect),
     url(
@@ -91,7 +98,8 @@ urlpatterns += [
         r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/edit/$', views.private_thread_redirect
     ),
     url(
-        r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', views.private_thread_redirect
+        r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$',
+        views.private_thread_redirect
     ),
     url(
         r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/vote/$', views.private_thread_redirect
@@ -119,7 +127,9 @@ urlpatterns += [
         r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$',
         views.private_thread_redirect
     ),
-    url(r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', views.private_thread_redirect),
+    url(
+        r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', views.private_thread_redirect
+    ),
     url(
         r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/moderated/$',
         views.private_thread_redirect
@@ -133,7 +143,8 @@ urlpatterns += [
         views.private_thread_redirect
     ),
     url(
-        r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', views.private_thread_redirect
+        r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$',
+        views.private_thread_redirect
     ),
     url(
         r'^private-threads/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$',

+ 3 - 1
misago/legal/tests.py

@@ -117,7 +117,9 @@ class TermsOfServiceTests(TestCase):
         settings.override_setting('terms_of_service', 'Lorem ipsum')
         context_dict = legal_links(MockRequest())
 
-        self.assertEqual(context_dict, {'TERMS_OF_SERVICE_URL': reverse('misago:terms-of-service')})
+        self.assertEqual(
+            context_dict, {'TERMS_OF_SERVICE_URL': reverse('misago:terms-of-service')}
+        )
 
     def test_context_processor_remote_tos(self):
         """context processor has TOS link to remote url"""

+ 3 - 1
misago/readtracker/categoriestracker.py

@@ -84,7 +84,9 @@ def sync_record(user, category):
             last_read_on = timezone.now()
         else:
             last_read_on = cutoff_date
-        category_record = user.categoryread_set.create(category=category, last_read_on=last_read_on)
+        category_record = user.categoryread_set.create(
+            category=category, last_read_on=last_read_on
+        )
 
 
 def read_category(user, category):

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

@@ -95,9 +95,7 @@ def clean_posts_for_merge(request, thread):
                     raise MergeError(authorship_error)
 
             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."))
 
             posts.append(post)

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

@@ -98,7 +98,9 @@ class PostingEndpoint(object):
     def save(self):
         """save new state to backend"""
         if not self._is_validated or self.errors:
-            raise RuntimeError("You need to validate posting data successfully before calling save")
+            raise RuntimeError(
+                "You need to validate posting data successfully before calling save"
+            )
 
         try:
             for middleware, obj in self.middlewares:

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

@@ -60,7 +60,9 @@ class AttachmentsSerializer(serializers.Serializer):
                     message = _(
                         "You don't have permission to remove \"%(attachment)s\" attachment."
                     )
-                    raise serializers.ValidationError(message % {'attachment': attachment.filename})
+                    raise serializers.ValidationError(
+                        message % {'attachment': attachment.filename}
+                    )
 
         if new_attachments:
             self.update_attachments = True

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

@@ -13,8 +13,7 @@ MIN_POSTING_PAUSE = 3
 
 class FloodProtectionMiddleware(PostingMiddleware):
     def use_this_middleware(self):
-        return not self.user.acl_cache['can_omit_flood_protection'
-                                       ] and self.mode != PostingEndpoint.EDIT
+        return not self.user.acl_cache['can_omit_flood_protection'] and self.mode != PostingEndpoint.EDIT
 
     def interrupt_posting(self, serializer):
         now = timezone.now()

+ 3 - 2
misago/threads/migrations/0001_initial.py

@@ -66,8 +66,9 @@ class Migration(migrations.Migration):
                     )
                 ),
                 (
-                    'mentions',
-                    models.ManyToManyField(related_name='mention_set', to=settings.AUTH_USER_MODEL)
+                    'mentions', models.ManyToManyField(
+                        related_name='mention_set', to=settings.AUTH_USER_MODEL
+                    )
                 ),
                 (
                     'poster', models.ForeignKey(

+ 2 - 1
misago/threads/paginator.py

@@ -11,7 +11,8 @@ class PostsPaginator(Paginator):
         per_page = int(per_page) - 1
         if orphans:
             orphans += 1
-        super(PostsPaginator, self).__init__(object_list, per_page, orphans, allow_empty_first_page)
+        super(PostsPaginator,
+              self).__init__(object_list, per_page, orphans, allow_empty_first_page)
 
     def page(self, number):
         """

+ 3 - 1
misago/threads/participants.py

@@ -27,7 +27,9 @@ def make_threads_participants_aware(user, threads):
         thread.participant = None
         threads_dict[thread.pk] = thread
 
-    participants_qs = ThreadParticipant.objects.filter(user=user, thread_id__in=threads_dict.keys())
+    participants_qs = ThreadParticipant.objects.filter(
+        user=user, thread_id__in=threads_dict.keys()
+    )
 
     for participant in participants_qs:
         participant.user = user

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

@@ -263,7 +263,9 @@ def allow_add_participant(user, target):
     message_format = {'user': target.username}
 
     if not can_use_private_threads(target):
-        raise PermissionDenied(_("%(user)s can't participate in private threads.") % message_format)
+        raise PermissionDenied(
+            _("%(user)s can't participate in private threads.") % message_format
+        )
 
     if user.acl_cache['can_add_everyone_to_private_threads']:
         return

+ 7 - 3
misago/threads/permissions/threads.py

@@ -102,8 +102,10 @@ class CategoryPermissionsForm(forms.Form):
     )
     can_hide_own_threads = forms.TypedChoiceField(
         label=_("Can hide own threads"),
-        help_text=_("Only threads started within time limit and "
-                    "with no replies can be hidden."),
+        help_text=_(
+            "Only threads started within time limit and "
+            "with no replies can be hidden."
+        ),
         coerce=int,
         initial=0,
         choices=((0, _("No")), (1, _("Hide threads")), (2, _("Delete threads")))
@@ -542,7 +544,9 @@ def allow_edit_thread(user, target):
     if user.is_anonymous:
         raise PermissionDenied(_("You have to sign in to edit threads."))
 
-    category_acl = user.acl_cache['categories'].get(target.category_id, {'can_edit_threads': False})
+    category_acl = user.acl_cache['categories'].get(
+        target.category_id, {'can_edit_threads': False}
+    )
 
     if not category_acl['can_edit_threads']:
         raise PermissionDenied(_("You can't edit threads in this category."))

+ 3 - 1
misago/threads/serializers/moderation.py

@@ -73,5 +73,7 @@ class NewThreadSerializer(serializers.Serializer):
             return is_closed  # don't validate closed further if category failed
 
         if is_closed and not self.category.acl.get('can_close_threads'):
-            raise ValidationError(_("You don't have permission to close threads in this category."))
+            raise ValidationError(
+                _("You don't have permission to close threads in this category.")
+            )
         return is_closed

+ 6 - 2
misago/threads/signals.py

@@ -118,7 +118,9 @@ def update_usernames(sender, **kwargs):
         editor_name=sender.username, editor_slug=sender.slug
     )
 
-    PostLike.objects.filter(liker=sender).update(liker_name=sender.username, liker_slug=sender.slug)
+    PostLike.objects.filter(liker=sender).update(
+        liker_name=sender.username, liker_slug=sender.slug
+    )
 
     Attachment.objects.filter(uploader=sender).update(
         uploader_name=sender.username, uploader_slug=sender.slug
@@ -126,7 +128,9 @@ def update_usernames(sender, **kwargs):
 
     Poll.objects.filter(poster=sender).update(poster_name=sender.username, poster_slug=sender.slug)
 
-    PollVote.objects.filter(voter=sender).update(voter_name=sender.username, voter_slug=sender.slug)
+    PollVote.objects.filter(voter=sender).update(
+        voter_name=sender.username, voter_slug=sender.slug
+    )
 
 
 @receiver(pre_delete, sender=get_user_model())

+ 6 - 6
misago/threads/tests/test_attachments_middleware.py

@@ -174,8 +174,8 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
         self.assertEqual(self.post.attachment_set.count(), 2)
 
         attachments_filenames = list(reversed([a.filename for a in attachments]))
-        self.assertEqual([a['filename'] for a in self.post.attachments_cache], attachments_filenames
-                         )
+        self.assertEqual([a['filename'] for a in self.post.attachments_cache],
+                         attachments_filenames)
 
     def test_remove_attachments(self):
         """middleware removes attachment from post and db"""
@@ -204,8 +204,8 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
         self.assertEqual(Attachment.objects.count(), 1)
 
         attachments_filenames = [attachments[0].filename]
-        self.assertEqual([a['filename'] for a in self.post.attachments_cache], attachments_filenames
-                         )
+        self.assertEqual([a['filename'] for a in self.post.attachments_cache],
+                         attachments_filenames)
 
     def test_steal_attachments(self):
         """middleware validates if attachments are already assigned to other posts"""
@@ -262,8 +262,8 @@ class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
         self.assertEqual(self.post.attachment_set.count(), 2)
 
         attachments_filenames = [attachments[2].filename, attachments[0].filename]
-        self.assertEqual([a['filename'] for a in self.post.attachments_cache], attachments_filenames
-                         )
+        self.assertEqual([a['filename'] for a in self.post.attachments_cache],
+                         attachments_filenames)
 
 
 class ValidateAttachmentsCountTests(AuthenticatedUserTestCase):

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

@@ -29,7 +29,9 @@ class EmailNotificationTests(AuthenticatedUserTestCase):
         )
         self.override_acl()
 
-        self.api_link = reverse('misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+        )
 
         self.other_user = UserModel.objects.create_user('Bob', 'bob@boberson.com', 'pass123')
 

+ 17 - 11
misago/threads/tests/test_gotoviews.py

@@ -26,8 +26,8 @@ class GotoPostTests(GotoViewTestCase):
         response = self.client.get(self.thread.first_post.get_absolute_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(
-            response['location'], GOTO_URL %
-            (self.thread.get_absolute_url(), self.thread.first_post_id)
+            response['location'],
+            GOTO_URL % (self.thread.get_absolute_url(), self.thread.first_post_id)
         )
 
         response = self.client.get(response['location'])
@@ -40,7 +40,9 @@ class GotoPostTests(GotoViewTestCase):
 
         response = self.client.get(post.get_absolute_url())
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk))
+        self.assertEqual(
+            response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk)
+        )
 
         response = self.client.get(response['location'])
         self.assertContains(response, post.get_absolute_url())
@@ -108,8 +110,8 @@ class GotoLastTests(GotoViewTestCase):
         response = self.client.get(self.thread.get_last_post_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(
-            response['location'], GOTO_URL %
-            (self.thread.get_absolute_url(), self.thread.first_post_id)
+            response['location'],
+            GOTO_URL % (self.thread.get_absolute_url(), self.thread.first_post_id)
         )
 
         response = self.client.get(response['location'])
@@ -122,7 +124,9 @@ class GotoLastTests(GotoViewTestCase):
 
         response = self.client.get(self.thread.get_last_post_url())
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk))
+        self.assertEqual(
+            response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk)
+        )
 
         response = self.client.get(response['location'])
         self.assertContains(response, post.get_absolute_url())
@@ -134,8 +138,8 @@ class GotoNewTests(GotoViewTestCase):
         response = self.client.get(self.thread.get_new_post_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(
-            response['location'], GOTO_URL %
-            (self.thread.get_absolute_url(), self.thread.first_post_id)
+            response['location'],
+            GOTO_URL % (self.thread.get_absolute_url(), self.thread.first_post_id)
         )
 
     def test_goto_first_new_post(self):
@@ -149,7 +153,9 @@ class GotoNewTests(GotoViewTestCase):
 
         response = self.client.get(self.thread.get_new_post_url())
         self.assertEqual(response.status_code, 302)
-        self.assertEqual(response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk))
+        self.assertEqual(
+            response['location'], GOTO_URL % (self.thread.get_absolute_url(), post.pk)
+        )
 
     def test_goto_first_new_post_on_next_page(self):
         """first unread post redirect url in already read multipage thread is valid"""
@@ -219,8 +225,8 @@ class GotoUnapprovedTests(GotoViewTestCase):
         response = self.client.get(self.thread.get_unapproved_post_url())
         self.assertEqual(response.status_code, 302)
         self.assertEqual(
-            response['location'], GOTO_URL %
-            (self.thread.get_absolute_url(), self.thread.first_post_id)
+            response['location'],
+            GOTO_URL % (self.thread.get_absolute_url(), self.thread.first_post_id)
         )
 
     def test_vie_handles_unapproved_posts(self):

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

@@ -85,7 +85,8 @@ class PostMentionsTests(AuthenticatedUserTestCase):
 
         mentions = ['@{}'.format(u) for u in users]
         response = self.client.post(
-            self.post_link, data={'post': "This is test response, {}!".format(', '.join(mentions))}
+            self.post_link,
+            data={'post': "This is test response, {}!".format(', '.join(mentions))}
         )
         self.assertEqual(response.status_code, 200)
 

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

@@ -105,7 +105,9 @@ class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
     def setUp(self):
         super(SubscribeRepliedThreadTests, self).setUp()
         self.thread = testutils.post_thread(self.category)
-        self.api_link = reverse('misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+        )
 
     def test_dont_subscribe(self):
         """middleware makes no subscription to thread"""

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

@@ -127,7 +127,9 @@ class EditReplyTests(AuthenticatedUserTestCase):
         self.post.save()
 
         response = self.put(self.api_link)
-        self.assertContains(response, "This post is protected. You can't edit it.", status_code=403)
+        self.assertContains(
+            response, "This post is protected. You can't edit it.", status_code=403
+        )
 
         # allow to post in closed thread
         self.override_acl({'can_protect_posts': 1})

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

@@ -16,7 +16,9 @@ class ThreadPollApiTestCase(AuthenticatedUserTestCase):
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.override_acl()
 
-        self.api_link = reverse('misago:api:thread-poll-list', kwargs={'thread_pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-poll-list', kwargs={'thread_pk': self.thread.pk}
+        )
 
     def post(self, url, data=None):
         return self.client.post(url, json.dumps(data or {}), content_type='application/json')

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

@@ -110,7 +110,8 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         self.assertEqual([c['votes'] for c in response_json], [1, 0, 2, 1])
         self.assertEqual([len(c['voters']) for c in response_json], [1, 0, 2, 1])
 
-        self.assertEqual([[v['username'] for v in c['voters']] for c in response_json][0][0], 'bob')
+        self.assertEqual([[v['username'] for v in c['voters']] for c in response_json][0][0],
+                         'bob')
 
         user = UserModel.objects.get(slug='bob')
 
@@ -134,7 +135,8 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
         self.assertEqual([c['votes'] for c in response_json], [1, 0, 2, 1])
         self.assertEqual([len(c['voters']) for c in response_json], [1, 0, 2, 1])
 
-        self.assertEqual([[v['username'] for v in c['voters']] for c in response_json][0][0], 'bob')
+        self.assertEqual([[v['username'] for v in c['voters']] for c in response_json][0][0],
+                         'bob')
 
         user = UserModel.objects.get(slug='bob')
 

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

@@ -125,9 +125,11 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def test_invalid_posts_ids(self):
         """api handles invalid post id"""
         response = self.client.post(
-            self.api_link, json.dumps({
+            self.api_link,
+            json.dumps({
                 'posts': [1, 2, 'string']
-            }), content_type="application/json"
+            }),
+            content_type="application/json"
         )
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400

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

@@ -20,7 +20,9 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)
 
-        self.api_link = reverse('misago:api:thread-post-move', kwargs={'thread_pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-post-move', kwargs={'thread_pk': self.thread.pk}
+        )
 
         Category(
             name='Category B',

+ 38 - 6
misago/threads/tests/test_thread_postpatch_api.py

@@ -440,7 +440,9 @@ class PostHideApiTests(ThreadPostPatchApiTestCase):
         self.assertEqual(response.status_code, 400)
 
         response_json = response.json()
-        self.assertEqual(response_json['detail'][0], "This post is protected. You can't reveal it.")
+        self.assertEqual(
+            response_json['detail'][0], "This post is protected. You can't reveal it."
+        )
 
         self.refresh_post()
         self.assertTrue(self.post.is_hidden)
@@ -689,19 +691,37 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         """api validates user's permission to see posts likes"""
         self.override_acl({'can_see_posts_likes': 0})
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'is-liked', 'value': True}])
+        response = self.patch(
+            self.api_link, [{
+                'op': 'replace',
+                'path': 'is-liked',
+                'value': True
+            }]
+        )
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
 
     def test_like_no_like_permission(self):
         """api validates user's permission to see posts likes"""
         self.override_acl({'can_like_posts': False})
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'is-liked', 'value': True}])
+        response = self.patch(
+            self.api_link, [{
+                'op': 'replace',
+                'path': 'is-liked',
+                'value': True
+            }]
+        )
         self.assertContains(response, "You can't like posts in this category.", status_code=400)
 
     def test_like_post(self):
         """api adds user like to post"""
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'is-liked', 'value': True}])
+        response = self.patch(
+            self.api_link, [{
+                'op': 'replace',
+                'path': 'is-liked',
+                'value': True
+            }]
+        )
         self.assertEqual(response.status_code, 200)
 
         response_json = response.json()
@@ -725,7 +745,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         testutils.like_post(self.post, username='Bob')
         testutils.like_post(self.post, username='Miku')
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'is-liked', 'value': True}])
+        response = self.patch(
+            self.api_link, [{
+                'op': 'replace',
+                'path': 'is-liked',
+                'value': True
+            }]
+        )
         self.assertEqual(response.status_code, 200)
 
         response_json = response.json()
@@ -777,7 +803,13 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
         """api does no state change if we are linking liked post"""
         testutils.like_post(self.post, self.user)
 
-        response = self.patch(self.api_link, [{'op': 'replace', 'path': 'is-liked', 'value': True}])
+        response = self.patch(
+            self.api_link, [{
+                'op': 'replace',
+                'path': 'is-liked',
+                'value': True
+            }]
+        )
         self.assertEqual(response.status_code, 200)
 
         response_json = response.json()

+ 3 - 2
misago/threads/tests/test_thread_postread_api.py

@@ -13,8 +13,9 @@ class PostReadApiTests(ThreadsApiTestCase):
         self.post = testutils.reply_thread(self.thread, poster=self.user, posted_on=timezone.now())
 
         self.api_link = reverse(
-            'misago:api:thread-post-read', kwargs={'thread_pk': self.thread.pk,
-                                                   'pk': self.post.pk}
+            'misago:api:thread-post-read',
+            kwargs={'thread_pk': self.thread.pk,
+                    'pk': self.post.pk}
         )
 
     def test_read_anonymous(self):

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

@@ -133,9 +133,11 @@ class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     def test_invalid_posts_ids(self):
         """api handles invalid post id"""
         response = self.client.post(
-            self.api_link, json.dumps({
+            self.api_link,
+            json.dumps({
                 'posts': [1, 2, 'string']
-            }), content_type="application/json"
+            }),
+            content_type="application/json"
         )
         self.assertContains(
             response, "One or more post ids received were invalid.", status_code=400

+ 6 - 2
misago/threads/tests/test_thread_reply_api.py

@@ -17,7 +17,9 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)
 
-        self.api_link = reverse('misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk})
+        self.api_link = reverse(
+            'misago:api:thread-post-list', kwargs={'thread_pk': self.thread.pk}
+        )
 
     def override_acl(self, extra_acl=None):
         new_acl = self.user.acl_cache
@@ -72,7 +74,9 @@ class ReplyThreadTests(AuthenticatedUserTestCase):
 
         response = self.client.post(self.api_link)
         self.assertContains(
-            response, "This category is closed. You can't reply to threads in it.", status_code=403
+            response,
+            "This category is closed. You can't reply to threads in it.",
+            status_code=403
         )
 
         # allow to post in closed category

+ 6 - 2
misago/threads/tests/test_threads_editor_api.py

@@ -328,7 +328,9 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
         response = self.client.get(self.api_link)
         self.assertContains(
-            response, "This category is closed. You can't reply to threads in it.", status_code=403
+            response,
+            "This category is closed. You can't reply to threads in it.",
+            status_code=403
         )
 
         # allow to post in closed category
@@ -500,7 +502,9 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.post.save()
 
         response = self.client.get(self.api_link)
-        self.assertContains(response, "This post is protected. You can't edit it.", status_code=403)
+        self.assertContains(
+            response, "This post is protected. You can't edit it.", status_code=403
+        )
 
         # allow to post in closed thread
         self.override_acl({'can_edit_posts': 1, 'can_protect_posts': 1})

+ 6 - 2
misago/threads/tests/test_threads_merge_api.py

@@ -59,7 +59,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(response.status_code, 403)
 
         response_json = response.json()
-        self.assertEqual(response_json, {'detail': "One or more thread ids received were invalid."})
+        self.assertEqual(
+            response_json, {'detail': "One or more thread ids received were invalid."}
+        )
 
         response = self.client.post(
             self.api_link,
@@ -71,7 +73,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         self.assertEqual(response.status_code, 403)
 
         response_json = response.json()
-        self.assertEqual(response_json, {'detail': "One or more thread ids received were invalid."})
+        self.assertEqual(
+            response_json, {'detail': "One or more thread ids received were invalid."}
+        )
 
     def test_merge_single_thread(self):
         """api validates if we are trying to merge single thread"""

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

@@ -362,8 +362,7 @@ class AllThreadsListTests(ThreadsListTestCase):
 
         for thread in threads[:settings.MISAGO_THREADS_PER_PAGE]:
             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[settings.MISAGO_THREADS_PER_PAGE:settings.MISAGO_THREADS_PER_PAGE * 2]:
             self.assertContains(response, thread.get_absolute_url())
         for thread in threads[settings.MISAGO_THREADS_PER_PAGE * 2:]:
             self.assertNotContains(response, thread.get_absolute_url())

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

@@ -51,7 +51,7 @@ class ThreadVisibilityTests(ThreadViewTestCase):
         self.assertContains(response, self.thread.title)
 
     def test_view_shows_owner_thread(self):
-        """view handles "owned threads only" """
+        """view handles "owned threads" only"""
         self.override_acl({'can_see_all_threads': 0})
 
         response = self.client.get(self.thread.get_absolute_url())

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

@@ -72,7 +72,8 @@ class TreesMapTests(TestCase):
         thread_type = trees_map.get_type_for_tree_id(tree_id)
 
         self.assertEqual(
-            thread_type.root_name, 'root_category', "returned invalid thread type for given tree id"
+            thread_type.root_name, 'root_category',
+            "returned invalid thread type for given tree id"
         )
 
         try:

+ 2 - 1
misago/threads/urls/__init__.py

@@ -47,7 +47,8 @@ else:
 
 urlpatterns += threads_list_patterns(
     'category', CategoryThreadsList, (
-        r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/$', r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/my/$',
+        r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/$',
+        r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/my/$',
         r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/new/$',
         r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/unread/$',
         r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/subscribed/$',

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

@@ -10,7 +10,9 @@ router = MisagoApiRouter()
 router.register(r'attachments', AttachmentViewSet, base_name='attachment')
 
 router.register(r'threads', ThreadViewSet, base_name='thread')
-router.register(r'threads/(?P<thread_pk>[^/.]+)/posts', ThreadPostsViewSet, base_name='thread-post')
+router.register(
+    r'threads/(?P<thread_pk>[^/.]+)/posts', ThreadPostsViewSet, base_name='thread-post'
+)
 router.register(r'threads/(?P<thread_pk>[^/.]+)/poll', ThreadPollViewSet, base_name='thread-poll')
 
 router.register(r'private-threads', PrivateThreadViewSet, base_name='private-thread')

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

@@ -50,8 +50,9 @@ class ViewModel(BaseViewModel):
 class ThreadsRootCategory(ViewModel):
     def get_categories(self, request):
         return [Category.objects.root_category()] + list(
-            Category.objects.all_categories().
-            filter(id__in=request.user.acl_cache['browseable_categories']).select_related('parent')
+            Category.objects.all_categories().filter(
+                id__in=request.user.acl_cache['browseable_categories']
+            ).select_related('parent')
         )
 
 

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

@@ -107,8 +107,8 @@ class ViewModel(object):
         return LISTS_NAMES[list_type]
 
     def get_base_queryset(self, request, threads_categories, list_type):
-        return get_threads_queryset(request.user, threads_categories, list_type
-                                    ).order_by('-last_post_id')
+        return get_threads_queryset(request.user, threads_categories,
+                                    list_type).order_by('-last_post_id')
 
     def get_pinned_threads(self, queryset, category, threads_categories):
         return []
@@ -162,8 +162,8 @@ class ForumThreads(ViewModel):
 
 class PrivateThreads(ViewModel):
     def get_base_queryset(self, request, threads_categories, list_type):
-        queryset = super(PrivateThreads, self
-                         ).get_base_queryset(request, threads_categories, list_type)
+        queryset = super(PrivateThreads,
+                         self).get_base_queryset(request, threads_categories, list_type)
 
         # limit queryset to threads we are participant of
         participated_threads = request.user.threadparticipant_set.values('thread_id')

+ 3 - 1
misago/users/api/userendpoints/changepassword.py

@@ -13,7 +13,9 @@ def change_password_endpoint(request, pk=None):
     serializer = ChangePasswordSerializer(data=request.data, context={'user': request.user})
 
     if serializer.is_valid():
-        token = store_new_credential(request, 'password', serializer.validated_data['new_password'])
+        token = store_new_credential(
+            request, 'password', serializer.validated_data['new_password']
+        )
 
         mail_subject = _("Confirm password change on %(forum_name)s forums")
         mail_subject = mail_subject % {'forum_name': settings.forum_name}

+ 1 - 1
misago/users/avatars/uploaded.py

@@ -38,7 +38,7 @@ def validate_dimensions(uploaded_file):
 
     min_size = max(settings.MISAGO_AVATARS_SIZES)
     if min(image.size) < min_size:
-        message = _("Uploaded image should be at " "least %(size)s pixels tall and wide.")
+        message = _("Uploaded image should be at least %(size)s pixels tall and wide.")
         raise ValidationError(message % {'size': min_size})
 
     if image.size[0] * image.size[1] > 2000 * 3000:

+ 3 - 1
misago/users/forms/auth.py

@@ -84,7 +84,9 @@ class AdminAuthenticationForm(AuthenticationForm):
     required_css_class = 'required'
 
     def __init__(self, *args, **kwargs):
-        self.error_messages.update({'not_staff': _("Your account does not have admin privileges.")})
+        self.error_messages.update({
+            'not_staff': _("Your account does not have admin privileges.")
+        })
 
         super(AdminAuthenticationForm, self).__init__(*args, **kwargs)
 

+ 7 - 2
misago/users/management/commands/createsuperuser.py

@@ -34,7 +34,10 @@ class Command(BaseCommand):
             help='Specifies the username for the superuser.'
         )
         parser.add_argument(
-            '--email', dest='email', default=None, help='Specifies the username for the superuser.'
+            '--email',
+            dest='email',
+            default=None,
+            help='Specifies the username for the superuser.'
         )
         parser.add_argument(
             '--password',
@@ -133,7 +136,9 @@ class Command(BaseCommand):
                 while not password:
                     try:
                         raw_value = getpass("Enter password: ").strip()
-                        validate_password(raw_value, user=UserModel(username=username, email=email))
+                        validate_password(
+                            raw_value, user=UserModel(username=username, email=email)
+                        )
 
                         repeat_raw_value = getpass("Repeat password: ").strip()
                         if raw_value != repeat_raw_value:

+ 3 - 1
misago/users/models/user.py

@@ -24,7 +24,9 @@ from .rank import Rank
 
 class UserManager(BaseUserManager):
     @transaction.atomic
-    def create_user(self, username, email, password=None, set_default_avatar=False, **extra_fields):
+    def create_user(
+            self, username, email, password=None, set_default_avatar=False, **extra_fields
+    ):
         from misago.users.validators import validate_email, validate_username
 
         email = self.normalize_email(email)

+ 2 - 3
misago/users/permissions/moderation.py

@@ -174,9 +174,8 @@ def allow_lift_ban(user, target):
         if not ban.valid_until:
             raise PermissionDenied(_("You can't lift permanent bans."))
         elif ban.valid_until > lift_cutoff:
-            message = _("You can't lift bans that " "expire after %(expiration)s.")
-            message = message % {'expiration': format_date(lift_cutoff)}
-            raise PermissionDenied(message)
+            message = _("You can't lift bans that expire after %(expiration)s.")
+            raise PermissionDenied(message % {'expiration': format_date(lift_cutoff)})
 
 
 can_lift_ban = return_boolean(allow_lift_ban)

+ 1 - 2
misago/users/search.py

@@ -46,8 +46,7 @@ def search_users(**filters):
     # lets grab head and tail results:
     results += list(queryset.filter(slug__startswith=username)[:HEAD_RESULTS])
     results += list(
-        queryset.filter(slug__contains=username).exclude(pk__in=[r.pk
-                                                                 for r in results])[:TAIL_RESULTS]
+        queryset.filter(slug__contains=username).exclude(pk__in=[r.pk for r in results])[:TAIL_RESULTS]
     )
 
     return results

+ 2 - 3
misago/users/serializers/auth.py

@@ -32,9 +32,8 @@ class AuthenticatedUserSerializer(UserSerializer, AuthFlags):
     class Meta:
         model = UserModel
         fields = UserSerializer.Meta.fields + (
-            'is_hiding_presence', 'limits_private_thread_invites_to',
-            'subscribe_to_started_threads', 'subscribe_to_replied_threads', 'is_authenticated',
-            'is_anonymous',
+            'is_hiding_presence', 'limits_private_thread_invites_to', 'subscribe_to_started_threads',
+            'subscribe_to_replied_threads', 'is_authenticated', 'is_anonymous',
         )
 
     def get_acl(self, obj):

+ 3 - 1
misago/users/tests/test_auth_api.py

@@ -68,7 +68,9 @@ class GatewayTests(TestCase):
         response_json = response.json()
         self.assertEqual(response_json['code'], 'banned')
         self.assertEqual(response_json['detail']['message']['plain'], ban.user_message)
-        self.assertEqual(response_json['detail']['message']['html'], '<p>%s</p>' % ban.user_message)
+        self.assertEqual(
+            response_json['detail']['message']['html'], '<p>%s</p>' % ban.user_message
+        )
 
         response = self.client.get('/api/auth/')
         self.assertEqual(response.status_code, 200)

+ 19 - 6
misago/users/tests/test_user_avatar_api.py

@@ -83,7 +83,9 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         response = self.client.get(self.link)
         self.assertContains(response, "You have to sign in", status_code=403)
 
-        self.login_user(UserModel.objects.create_user("BobUser", "bob@bob.com", self.USER_PASSWORD))
+        self.login_user(
+            UserModel.objects.create_user("BobUser", "bob@bob.com", self.USER_PASSWORD)
+        )
 
         response = self.client.get(self.link)
         self.assertContains(response, "can't change other users avatars", status_code=403)
@@ -227,7 +229,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.assertContains(response, "Incorrect image.", status_code=400)
 
         # invalid id is handled
-        response = self.client.post(self.link, data={'avatar': 'galleries', 'image': 'asdsadsadsa'})
+        response = self.client.post(
+            self.link, data={'avatar': 'galleries',
+                             'image': 'asdsadsadsa'}
+        )
         self.assertContains(response, "Incorrect image.", status_code=400)
 
         # nonexistant image is handled
@@ -330,7 +335,9 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)
-        self.assertEqual(options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message)
+        self.assertEqual(
+            options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
+        )
 
         override_acl(self.user, {
             'can_moderate_avatars': 1,
@@ -356,7 +363,9 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)
-        self.assertEqual(options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message)
+        self.assertEqual(
+            options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
+        )
 
         override_acl(self.user, {
             'can_moderate_avatars': 1,
@@ -382,7 +391,9 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)
-        self.assertEqual(options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message)
+        self.assertEqual(
+            options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
+        )
 
         override_acl(self.user, {
             'can_moderate_avatars': 1,
@@ -404,7 +415,9 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         self.assertEqual(options['avatars'], other_user.avatars)
         self.assertEqual(options['is_avatar_locked'], other_user.is_avatar_locked)
         self.assertEqual(options['avatar_lock_user_message'], other_user.avatar_lock_user_message)
-        self.assertEqual(options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message)
+        self.assertEqual(
+            options['avatar_lock_staff_message'], other_user.avatar_lock_staff_message
+        )
 
     def test_moderate_own_avatar(self):
         """moderate own avatar"""

+ 3 - 1
misago/users/tests/test_useradmin_views.py

@@ -515,7 +515,9 @@ class UserAdminViewsTests(AdminTestCase):
     def test_delete_posts_view(self):
         """delete user posts view deletes posts"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:delete-posts', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:delete-posts', kwargs={'pk': test_user.pk}
+        )
 
         category = Category.objects.all_categories()[:1][0]
         thread = post_thread(category)

+ 2 - 4
misago/users/validators.py

@@ -86,8 +86,7 @@ def validate_username_length(value):
             "Username must be at least %(limit_value)s characters long.",
             settings.username_length_min
         )
-        message = message % {'limit_value': settings.username_length_min}
-        raise ValidationError(message)
+        raise ValidationError(message % {'limit_value': settings.username_length_min})
 
     if len(value) > settings.username_length_max:
         message = ungettext(
@@ -95,8 +94,7 @@ def validate_username_length(value):
             "Username cannot be longer than %(limit_value)s characters.",
             settings.username_length_max
         )
-        message = message % {'limit_value': settings.username_length_max}
-        raise ValidationError(message)
+        raise ValidationError(message % {'limit_value': settings.username_length_max})
 
 
 def validate_username(value, exclude=None):

+ 2 - 4
misago/users/views/activation.py

@@ -42,16 +42,14 @@ def activate_by_token(request, pk, token):
     try:
         if not inactive_user.requires_activation:
             message = _("%(user)s, your account is already active.")
-            message = message % {'user': inactive_user.username}
-            raise ActivationStopped(message)
+            raise ActivationStopped(message % {'user': inactive_user.username})
 
         if not is_activation_token_valid(inactive_user, token):
             message = _(
                 "%(user)s, your activation link is invalid. "
                 "Try again or request new activation link."
             )
-            message = message % {'user': inactive_user.username}
-            raise ActivationError(message)
+            raise ActivationError(message % {'user': inactive_user.username})
 
         ban = get_user_ban(inactive_user)
         if ban:

+ 4 - 9
misago/users/views/forgottenpassword.py

@@ -35,17 +35,12 @@ def reset_password_form(request, pk, token):
 
     try:
         if (request.user.is_authenticated and request.user.id != requesting_user.id):
-            message = _(
-                "%(user)s, your link has expired. "
-                "Please request new link and try again."
-            )
-            message = message % {'user': requesting_user.username}
-            raise ResetError(message)
+            message = _("%(user)s, your link has expired. Please request new link and try again.")
+            raise ResetError(message % {'user': requesting_user.username})
 
         if not is_password_change_token_valid(requesting_user, token):
-            message = _("%(user)s, your link is invalid. " "Please try again or request new link.")
-            message = message % {'user': requesting_user.username}
-            raise ResetError(message)
+            message = _("%(user)s, your link is invalid. Please try again or request new link.")
+            raise ResetError(message % {'user': requesting_user.username})
 
         ban = get_user_ban(requesting_user)
         if ban: