Browse Source

#702: did pass on users app

Rafał Pitoń 8 years ago
parent
commit
1c32143548
73 changed files with 1144 additions and 464 deletions
  1. 1 0
      misago/categories/signals.py
  2. 12 4
      misago/conf/forms.py
  3. 1 0
      misago/conf/tests/test_migrationutils.py
  4. 4 1
      misago/conf/tests/test_models.py
  5. 2 1
      misago/threads/api/postendpoints/merge.py
  6. 4 2
      misago/threads/api/postendpoints/move.py
  7. 2 1
      misago/threads/api/postendpoints/split.py
  8. 4 2
      misago/threads/api/threadendpoints/merge.py
  9. 1 3
      misago/threads/signals.py
  10. 51 18
      misago/users/api/auth.py
  11. 3 1
      misago/users/api/rest_permissions.py
  12. 40 17
      misago/users/api/userendpoints/avatar.py
  13. 3 1
      misago/users/api/usernamechanges.py
  14. 21 3
      misago/users/api/users.py
  15. 1 1
      misago/users/avatars/__init__.py
  16. 2 2
      misago/users/avatars/dynamic.py
  17. 5 2
      misago/users/avatars/store.py
  18. 7 13
      misago/users/bans.py
  19. 0 6
      misago/users/djangoadmin.py
  20. 2 1
      misago/users/forms/admin.py
  21. 1 1
      misago/users/forms/auth.py
  22. 1 1
      misago/users/forms/register.py
  23. 11 12
      misago/users/management/commands/createsuperuser.py
  24. 9 2
      misago/users/management/commands/synchronizeusers.py
  25. 4 2
      misago/users/models/ban.py
  26. 12 4
      misago/users/models/user.py
  27. 12 15
      misago/users/permissions/delete.py
  28. 3 3
      misago/users/permissions/moderation.py
  29. 1 1
      misago/users/permissions/profiles.py
  30. 4 1
      misago/users/serializers/ban.py
  31. 6 3
      misago/users/serializers/moderation.py
  32. 11 2
      misago/users/serializers/user.py
  33. 1 1
      misago/users/serializers/usernamechange.py
  34. 1 3
      misago/users/signals.py
  35. 104 27
      misago/users/tests/test_auth_api.py
  36. 20 5
      misago/users/tests/test_auth_backend.py
  37. 17 3
      misago/users/tests/test_auth_views.py
  38. 2 1
      misago/users/tests/test_avatars.py
  39. 12 9
      misago/users/tests/test_avatarserver_views.py
  40. 21 9
      misago/users/tests/test_banadmin_views.py
  41. 41 16
      misago/users/tests/test_bans.py
  42. 1 2
      misago/users/tests/test_commands.py
  43. 3 2
      misago/users/tests/test_createsuperuser.py
  44. 10 2
      misago/users/tests/test_decorators.py
  45. 1 1
      misago/users/tests/test_djangoadmin_auth.py
  46. 4 4
      misago/users/tests/test_forgottenpassword_views.py
  47. 4 1
      misago/users/tests/test_invalidatebans.py
  48. 30 6
      misago/users/tests/test_lists_views.py
  49. 23 5
      misago/users/tests/test_options_views.py
  50. 52 15
      misago/users/tests/test_profile_views.py
  51. 63 18
      misago/users/tests/test_rankadmin_views.py
  52. 34 8
      misago/users/tests/test_rest_permissions.py
  53. 9 2
      misago/users/tests/test_search.py
  54. 36 21
      misago/users/tests/test_user_avatar_api.py
  55. 31 11
      misago/users/tests/test_user_changeemail_api.py
  56. 23 9
      misago/users/tests/test_user_changepassword_api.py
  57. 40 23
      misago/users/tests/test_user_create_api.py
  58. 66 14
      misago/users/tests/test_user_feeds_api.py
  59. 6 1
      misago/users/tests/test_user_model.py
  60. 18 3
      misago/users/tests/test_user_signature_api.py
  61. 34 11
      misago/users/tests/test_user_username_api.py
  62. 88 27
      misago/users/tests/test_useradmin_views.py
  63. 47 17
      misago/users/tests/test_users_api.py
  64. 8 2
      misago/users/tests/test_validators.py
  65. 9 15
      misago/users/tokens.py
  66. 4 16
      misago/users/validators.py
  67. 2 2
      misago/users/viewmodels/activeposters.py
  68. 1 1
      misago/users/viewmodels/posts.py
  69. 5 2
      misago/users/viewmodels/rankusers.py
  70. 8 3
      misago/users/viewmodels/threads.py
  71. 4 2
      misago/users/views/activation.py
  72. 18 16
      misago/users/views/admin/users.py
  73. 2 2
      misago/users/views/options.py

+ 1 - 0
misago/categories/signals.py

@@ -9,6 +9,7 @@ delete_category_content = Signal()
 move_category_content = Signal(providing_args=["new_category"])
 
 
+# Signal handlers
 @receiver(username_changed)
 def update_usernames(sender, **kwargs):
     Category.objects.filter(last_poster=sender).update(

+ 12 - 4
misago/conf/forms.py

@@ -19,14 +19,16 @@ class ValidateChoicesNum(object):
         if self.min_choices and self.min_choices > data_len:
             message = ungettext(
                 'You have to select at least %(choices)d option.',
-                'You have to select at least %(choices)d options.', self.min_choices
+                'You have to select at least %(choices)d options.',
+                self.min_choices,
             )
             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
+                'You cannot select more than %(choices)d options.',
+                self.max_choices,
             )
             raise forms.ValidationError(message % {'choices': self.max_choices})
 
@@ -149,7 +151,10 @@ def ChangeSettingsForm(data=None, group=None):
     for setting in group.setting_set.order_by('order'):
         if setting.legend and setting.legend != fieldset_legend:
             if fieldset_fields:
-                fieldsets.append({'legend': fieldset_legend, 'form': fieldset_form(data)})
+                fieldsets.append({
+                    'legend': fieldset_legend,
+                    'form': fieldset_form(data),
+                })
             fieldset_legend = setting.legend
             fieldset_form = FormType
             fieldset_fields = False
@@ -157,6 +162,9 @@ def ChangeSettingsForm(data=None, group=None):
         fieldset_form = setting_field(fieldset_form, setting)
 
     if fieldset_fields:
-        fieldsets.append({'legend': fieldset_legend, 'form': fieldset_form(data)})
+        fieldsets.append({
+            'legend': fieldset_legend,
+            'form': fieldset_form(data),
+        })
 
     return fieldsets

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

@@ -85,6 +85,7 @@ class DBConfMigrationUtilsTests(TestCase):
         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']
         )

+ 4 - 1
misago/conf/tests/test_models.py

@@ -25,7 +25,9 @@ class SettingModelTests(TestCase):
         self.assertEqual(setting_model.value, ['Arthur', 'Patsy'])
 
         setting_model = Setting(
-            python_type='list', dry_value='Arthur,Robin,Patsy', default_value='Arthur,Patsy'
+            python_type='list',
+            dry_value='Arthur,Robin,Patsy',
+            default_value='Arthur,Patsy',
         )
         self.assertEqual(setting_model.value, ['Arthur', 'Robin', 'Patsy'])
 
@@ -40,6 +42,7 @@ class SettingModelTests(TestCase):
         setting_model.value = 3000
         self.assertEqual(setting_model.value, 3000)
         self.assertEqual(setting_model.dry_value, '3000')
+
         setting_model.value = None
         self.assertEqual(setting_model.value, 9001)
         self.assertEqual(setting_model.dry_value, None)

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

@@ -67,7 +67,8 @@ def clean_posts_for_merge(request, thread):
     elif len(posts_ids) > MERGE_LIMIT:
         message = ungettext(
             "No more than %(limit)s post can be merged at single time.",
-            "No more than %(limit)s posts can be merged at single time.", MERGE_LIMIT
+            "No more than %(limit)s posts can be merged at single time.",
+            MERGE_LIMIT,
         )
         raise MergeError(message % {'limit': MERGE_LIMIT})
 

+ 4 - 2
misago/threads/api/postendpoints/move.py

@@ -56,7 +56,8 @@ def clean_thread_for_move(request, thread, viewmodel):
     except Http404:
         raise PermissionDenied(
             _(
-                "The thread you have entered link to doesn't exist or you don't have permission to see it."
+                "The thread you have entered link to doesn't "
+                "exist or you don't have permission to see it."
             )
         )
 
@@ -77,7 +78,8 @@ def clean_posts_for_move(request, thread):
     elif len(posts_ids) > MOVE_LIMIT:
         message = ungettext(
             "No more than %(limit)s post can be moved at single time.",
-            "No more than %(limit)s posts can be moved at single time.", MOVE_LIMIT
+            "No more than %(limit)s posts can be moved at single time.",
+            MOVE_LIMIT,
         )
         raise PermissionDenied(message % {'limit': MOVE_LIMIT})
 

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

@@ -47,7 +47,8 @@ def clean_posts_for_split(request, thread):
     elif len(posts_ids) > SPLIT_LIMIT:
         message = ungettext(
             "No more than %(limit)s post can be split at single time.",
-            "No more than %(limit)s posts can be split at single time.", SPLIT_LIMIT
+            "No more than %(limit)s posts can be split at single time.",
+            SPLIT_LIMIT,
         )
         raise SplitError(message % {'limit': SPLIT_LIMIT})
 

+ 4 - 2
misago/threads/api/threadendpoints/merge.py

@@ -51,7 +51,8 @@ def thread_merge_endpoint(request, thread, viewmodel):
         return Response({
             'detail':
                 _(
-                    "The thread you have entered link to doesn't exist or you don't have permission to see it."
+                    "The thread you have entered link to doesn't "
+                    "exist or you don't have permission to see it."
                 )
         },
                         status=400)
@@ -146,7 +147,8 @@ def clean_threads_for_merge(request):
     elif len(threads_ids) > MERGE_LIMIT:
         message = ungettext(
             "No more than %(limit)s thread can be merged at single time.",
-            "No more than %(limit)s threads can be merged at single time.", MERGE_LIMIT
+            "No more than %(limit)s threads can be merged at single time.",
+            MERGE_LIMIT,
         )
         raise MergeError(message % {'limit': MERGE_LIMIT})
 

+ 1 - 3
misago/threads/signals.py

@@ -17,11 +17,9 @@ merge_post = Signal(providing_args=["other_post"])
 merge_thread = Signal(providing_args=["other_thread"])
 move_post = Signal()
 move_thread = Signal()
-"""
-Signal handlers
-"""
 
 
+# Signal handlers
 @receiver(merge_thread)
 def merge_threads_posts(sender, **kwargs):
     other_thread = kwargs['other_thread']

+ 51 - 18
misago/users/api/auth.py

@@ -42,9 +42,14 @@ def login(request):
     form = AuthenticationForm(request, data=request.data)
     if form.is_valid():
         auth.login(request, form.user_cache)
-        return Response(AuthenticatedUserSerializer(form.user_cache).data)
+        return Response(
+            AuthenticatedUserSerializer(form.user_cache).data,
+        )
     else:
-        return Response(form.get_errors_dict(), status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            form.get_errors_dict(),
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
 
 """
@@ -101,21 +106,30 @@ def send_activation(request):
     if form.is_valid():
         requesting_user = form.user_cache
 
-        mail_subject = _("Activate %(user)s account on %(forum_name)s forums")
-        subject_formats = {
+        mail_subject = _("Activate %(user)s account on %(forum_name)s forums") % {
             'user': requesting_user.username,
             'forum_name': settings.forum_name,
         }
-        mail_subject = mail_subject % subject_formats
 
         mail_user(
-            request, requesting_user, mail_subject, 'misago/emails/activation/by_user',
-            {'activation_token': make_activation_token(requesting_user)}
+            request,
+            requesting_user,
+            mail_subject,
+            'misago/emails/activation/by_user',
+            {
+                'activation_token': make_activation_token(requesting_user),
+            },
         )
 
-        return Response({'username': form.user_cache.username, 'email': form.user_cache.email})
+        return Response({
+            'username': form.user_cache.username,
+            'email': form.user_cache.email,
+        })
     else:
-        return Response(form.get_errors_dict(), status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            form.get_errors_dict(),
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
 
 """
@@ -132,23 +146,32 @@ def send_password_form(request):
     if form.is_valid():
         requesting_user = form.user_cache
 
-        mail_subject = _("Change %(user)s password on %(forum_name)s forums")
-        subject_formats = {
+        mail_subject = _("Change %(user)s password on %(forum_name)s forums") % {
             'user': requesting_user.username,
             'forum_name': settings.forum_name,
         }
-        mail_subject = mail_subject % subject_formats
 
         confirmation_token = make_password_change_token(requesting_user)
 
         mail_user(
-            request, requesting_user, mail_subject, 'misago/emails/change_password_form_link',
-            {'confirmation_token': confirmation_token}
+            request,
+            requesting_user,
+            mail_subject,
+            'misago/emails/change_password_form_link',
+            {
+                'confirmation_token': confirmation_token,
+            },
         )
 
-        return Response({'username': form.user_cache.username, 'email': form.user_cache.email})
+        return Response({
+            'username': form.user_cache.username,
+            'email': form.user_cache.email,
+        })
     else:
-        return Response(form.get_errors_dict(), status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            form.get_errors_dict(),
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
 
 """
@@ -184,7 +207,12 @@ def change_forgotten_password(request, pk, token):
         if get_user_ban(user):
             raise PasswordChangeFailed(expired_message)
     except PasswordChangeFailed as e:
-        return Response({'detail': e.args[0]}, status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            {
+                'detail': e.args[0],
+            },
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
     try:
         new_password = request.data.get('password', '').strip()
@@ -192,6 +220,11 @@ def change_forgotten_password(request, pk, token):
         user.set_password(new_password)
         user.save()
     except ValidationError as e:
-        return Response({'detail': e.messages[0]}, status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            {
+                'detail': e.messages[0],
+            },
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
     return Response({'username': user.username})

+ 3 - 1
misago/users/api/rest_permissions.py

@@ -13,7 +13,9 @@ class UnbannedOnly(BasePermission):
         ban = get_request_ip_ban(request)
         if ban:
             hydrated_ban = Ban(
-                check_type=Ban.IP, user_message=ban['message'], expires_on=ban['expires_on']
+                check_type=Ban.IP,
+                user_message=ban['message'],
+                expires_on=ban['expires_on'],
             )
             raise Banned(hydrated_ban)
 

+ 40 - 17
misago/users/api/userendpoints/avatar.py

@@ -20,11 +20,13 @@ def avatar_endpoint(request, pk=None):
         else:
             reason = None
 
-        return Response({
-            'detail': _("Your avatar is locked. You can't change it."),
-            'reason': reason
-        },
-                        status=status.HTTP_403_FORBIDDEN)
+        return Response(
+            {
+                'detail': _("Your avatar is locked. You can't change it."),
+                'reason': reason,
+            },
+            status=status.HTTP_403_FORBIDDEN,
+        )
 
     avatar_options = get_avatar_options(request.user)
     if request.method == 'POST':
@@ -41,7 +43,7 @@ def get_avatar_options(user):
         'crop_src': False,
         'crop_tmp': False,
         'upload': False,
-        'galleries': False
+        'galleries': False,
     }
 
     # Allow existing galleries
@@ -50,8 +52,14 @@ def get_avatar_options(user):
         for gallery in avatars.gallery.get_available_galleries():
             gallery_images = []
             for image in gallery['images']:
-                gallery_images.append({'id': image.id, 'url': image.url})
-            options['galleries'].append({'name': gallery['name'], 'images': gallery_images})
+                gallery_images.append({
+                    'id': image.id,
+                    'url': image.url,
+                })
+            options['galleries'].append({
+                'name': gallery['name'],
+                'images': gallery_images,
+            })
 
     # Can't have custom avatar?
     if not settings.allow_custom_avatars:
@@ -66,7 +74,7 @@ def get_avatar_options(user):
             options['crop_src'] = {
                 'url': user.avatar_src.url,
                 'crop': json.loads(user.avatar_crop),
-                'size': max(settings.MISAGO_AVATARS_SIZES)
+                'size': max(settings.MISAGO_AVATARS_SIZES),
             }
         except (TypeError, ValueError):
             pass
@@ -75,7 +83,7 @@ def get_avatar_options(user):
     if avatars.uploaded.has_temporary_avatar(user):
         options['crop_tmp'] = {
             'url': user.avatar_tmp.url,
-            'size': max(settings.MISAGO_AVATARS_SIZES)
+            'size': max(settings.MISAGO_AVATARS_SIZES),
         }
 
     # Allow upload conditions
@@ -96,19 +104,31 @@ def avatar_post(options, user, data):
     try:
         type_options = options[data.get('avatar', 'nope')]
         if not type_options:
-            return Response({
-                'detail': _("This avatar type is not allowed.")
-            },
-                            status=status.HTTP_400_BAD_REQUEST)
+            return Response(
+                {
+                    'detail': _("This avatar type is not allowed."),
+                },
+                status=status.HTTP_400_BAD_REQUEST,
+            )
 
         rpc_handler = AVATAR_TYPES[data.get('avatar', 'nope')]
     except KeyError:
-        return Response({'detail': _("Unknown avatar type.")}, status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            {
+                'detail': _("Unknown avatar type."),
+            },
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
     try:
         response_dict = {'detail': rpc_handler(user, data)}
     except AvatarError as e:
-        return Response({'detail': e.args[0]}, status=status.HTTP_400_BAD_REQUEST)
+        return Response(
+            {
+                'detail': e.args[0],
+            },
+            status=status.HTTP_400_BAD_REQUEST,
+        )
 
     user.save()
 
@@ -206,7 +226,10 @@ def moderate_avatar_endpoint(request, profile):
                 'avatar_lock_staff_message': profile.avatar_lock_staff_message,
             })
         else:
-            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+            return Response(
+                serializer.errors,
+                status=status.HTTP_400_BAD_REQUEST,
+            )
     else:
         return Response({
             'is_avatar_locked': int(profile.is_avatar_locked),

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

@@ -63,6 +63,8 @@ class UsernameChangesViewSet(viewsets.GenericViewSet):
         list_page = paginate(queryset, page, 12, 4)
 
         data = pagination_dict(list_page)
-        data.update({'results': UsernameChangeSerializer(list_page.object_list, many=True).data})
+        data.update({
+            'results': UsernameChangeSerializer(list_page.object_list, many=True).data,
+        })
 
         return Response(data)

+ 21 - 3
misago/users/api/users.py

@@ -280,7 +280,25 @@ class UserViewSet(viewsets.GenericViewSet):
 
 
 UserProfileSerializer = UserSerializer.subset_fields(
-    'id', 'username', 'slug', 'email', 'joined_on', 'rank', 'title', 'avatars', 'is_avatar_locked',
-    'signature', 'is_signature_locked', 'followers', 'following', 'threads', 'posts', 'acl',
-    'is_followed', 'is_blocked', 'status', 'absolute_url', 'api_url'
+    'id',
+    'username',
+    'slug',
+    'email',
+    'joined_on',
+    'rank',
+    'title',
+    'avatars',
+    'is_avatar_locked',
+    'signature',
+    'is_signature_locked',
+    'followers',
+    'following',
+    'threads',
+    'posts',
+    'acl',
+    'is_followed',
+    'is_blocked',
+    'status',
+    'absolute_url',
+    'api_url',
 )

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

@@ -7,7 +7,7 @@ AVATAR_TYPES = ('gravatar', 'dynamic', 'gallery', 'uploaded')
 SET_DEFAULT_AVATAR = {
     'gravatar': gravatar.set_avatar,
     'dynamic': dynamic.set_avatar,
-    'gallery': gallery.set_random_avatar
+    'gallery': gallery.set_random_avatar,
 }
 
 

+ 2 - 2
misago/users/avatars/dynamic.py

@@ -37,7 +37,7 @@ def draw_default(user):
 COLOR_WHEEL = (
     '#d32f2f', '#c2185b', '#7b1fa2', '#512da8', '#303f9f', '#1976d2', '#0288D1', '#0288d1',
     '#0097a7', '#00796b', '#388e3c', '#689f38', '#afb42b', '#fbc02d', '#ffa000', '#f57c00',
-    '#e64a19'
+    '#e64a19',
 )
 COLOR_WHEEL_LEN = len(COLOR_WHEEL)
 
@@ -68,7 +68,7 @@ def draw_avatar_flavour(user, image):
     font = ImageFont.truetype(FONT_FILE, size=size)
 
     text_size = font.getsize(string)
-    text_pos = ((image_size - text_size[0]) / 2, (image_size - text_size[1]) / 2)
+    text_pos = ((image_size - text_size[0]) / 2, (image_size - text_size[1]) / 2, )
 
     writer = ImageDraw.Draw(image)
     writer.text(text_pos, string, font=font)

+ 5 - 2
misago/users/avatars/store.py

@@ -40,8 +40,11 @@ def store_avatar(user, image):
         image.save(image_stream, "PNG")
 
         avatars.append(
-            Avatar.objects.
-            create(user=user, size=size, image=ContentFile(image_stream.getvalue(), 'avatar'))
+            Avatar.objects.create(
+                user=user,
+                size=size,
+                image=ContentFile(image_stream.getvalue(), 'avatar'),
+            )
         )
 
     user.avatars = [{'size': a.size, 'url': a.url} for a in avatars]

+ 7 - 13
misago/users/bans.py

@@ -82,15 +82,13 @@ def _set_user_ban_cache(user):
     return ban_cache
 
 
-"""
-Utility for checking if request came from banned IP
-
-This check may be performed frequently, which is why there is extra
-boilerplate that caches ban check result in session
-"""
-
-
 def get_request_ip_ban(request):
+    """
+    Utility for checking if request came from banned IP
+
+    This check may be performed frequently, which is why there is extra
+    boilerplate that caches ban check result in session
+    """
     session_ban_cache = _get_session_bancache(request)
     if session_ban_cache:
         if session_ban_cache['is_banned']:
@@ -148,11 +146,7 @@ def _hydrate_session_cache(ban_cache):
     return hydrated
 
 
-"""
-Utilities for front-end based bans
-"""
-
-
+# Utilities for front-end based bans
 def ban_user(user, user_message=None, staff_message=None, length=None, expires_on=None):
     if not expires_on and length:
         expires_on = timezone.now() + timedelta(**length)

+ 0 - 6
misago/users/djangoadmin.py

@@ -47,15 +47,9 @@ class UserAdminForm(forms.ModelForm):
         field.label = ''
         field.widget.render = self.render_edit_from_misago_link
 
-    # noinspection PyUnusedLocal
     def render_edit_from_misago_link(self, *args, **kwargs):
         """
         Composes an html hyperlink for the pseudo-field render.
-
-        `*args` and `**kwargs` arguments need to mimic widget `render()`
-        signature.
-
-        :rtype: str
         """
         text = _('Edit this user in Misago admin panel')
         link_html_template = ('<a href="{}" target="blank">' + text + '</a>')

+ 2 - 1
misago/users/forms/admin.py

@@ -189,7 +189,8 @@ class EditUserForm(UserBaseForm):
             raise forms.ValidationError(
                 ungettext(
                     "Signature can't be longer than %(limit)s character.",
-                    "Signature can't be longer than %(limit)s characters.", length_limit
+                    "Signature can't be longer than %(limit)s characters.",
+                    length_limit,
                 ) % {'limit': length_limit}
             )
 

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

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

+ 1 - 1
misago/users/forms/register.py

@@ -27,7 +27,7 @@ class RegisterForm(forms.Form):
                 user=UserModel(
                     username=cleaned_data.get('username'),
                     email=cleaned_data.get('email'),
-                )
+                ),
             )
 
     def clean(self):

+ 11 - 12
misago/users/management/commands/createsuperuser.py

@@ -31,19 +31,19 @@ class Command(BaseCommand):
             '--username',
             dest='username',
             default=None,
-            help='Specifies the username for the superuser.'
+            help="Specifies the username for the superuser.",
         )
         parser.add_argument(
             '--email',
             dest='email',
             default=None,
-            help='Specifies the username for the superuser.'
+            help="Specifies the username for the superuser.",
         )
         parser.add_argument(
             '--password',
             dest='password',
             default=None,
-            help='Specifies the username for the superuser.'
+            help="Specifies the username for the superuser.",
         )
         parser.add_argument(
             '--noinput',
@@ -51,21 +51,20 @@ class Command(BaseCommand):
             dest='interactive',
             default=True,
             help=(
-                'Tells Misago to NOT prompt the user for input '
-                'of any kind. You must use --username with '
-                '--noinput, along with an option for any other '
-                'required field. Superusers created with '
-                '--noinput will  not be able to log in until '
-                'they\'re given a valid password.'
-            )
+                "Tells Misago to NOT prompt the user for input "
+                "of any kind. You must use --username with "
+                "--noinput, along with an option for any other "
+                "required field. Superusers created with "
+                "--noinput will  not be able to log in until "
+                "they're given a valid password."
+            ),
         )
         parser.add_argument(
             '--database',
             action='store',
             dest='database',
             default=DEFAULT_DB_ALIAS,
-            help=('Specifies the database to use. '
-                  'Default is "default".')
+            help=('Specifies the database to use. Default is "default".'),
         )
 
     def execute(self, *args, **options):

+ 9 - 2
misago/users/management/commands/synchronizeusers.py

@@ -33,13 +33,20 @@ class Command(BaseCommand):
         start_time = time.time()
         for user in batch_update(UserModel.objects.all()):
             user.threads = user.thread_set.filter(
-                category__in=categories, is_hidden=False, is_unapproved=False
+                category__in=categories,
+                is_hidden=False,
+                is_unapproved=False,
             ).count()
+
             user.posts = user.post_set.filter(
-                category__in=categories, is_event=False, is_unapproved=False
+                category__in=categories,
+                is_event=False,
+                is_unapproved=False,
             ).count()
+
             user.followers = user.followed_by.count()
             user.following = user.follows.count()
+
             user.save()
 
             synchronized_count += 1

+ 4 - 2
misago/users/models/ban.py

@@ -107,7 +107,9 @@ class Ban(models.Model):
 
 class BanCache(models.Model):
     user = models.OneToOneField(
-        settings.AUTH_USER_MODEL, primary_key=True, related_name='ban_cache'
+        settings.AUTH_USER_MODEL,
+        primary_key=True,
+        related_name='ban_cache',
     )
     ban = models.ForeignKey(Ban, null=True, blank=True, on_delete=models.SET_NULL)
     bans_version = models.PositiveIntegerField(default=0)
@@ -128,7 +130,7 @@ class BanCache(models.Model):
             check_type=Ban.USERNAME,
             user_message=self.user_message,
             staff_message=self.staff_message,
-            expires_on=self.expires_on
+            expires_on=self.expires_on,
         )
         return BanMessageSerializer(temp_ban).data
 

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

@@ -196,10 +196,16 @@ class User(AbstractBaseUser, PermissionsMixin):
     is_active_staff_message = models.TextField(null=True, blank=True)
 
     avatar_tmp = models.ImageField(
-        max_length=255, upload_to=avatars.store.upload_to, null=True, blank=True
+        max_length=255,
+        upload_to=avatars.store.upload_to,
+        null=True,
+        blank=True,
     )
     avatar_src = models.ImageField(
-        max_length=255, upload_to=avatars.store.upload_to, null=True, blank=True
+        max_length=255,
+        upload_to=avatars.store.upload_to,
+        null=True,
+        blank=True,
     )
     avatar_crop = models.CharField(max_length=255, null=True, blank=True)
     avatars = JSONField(null=True, blank=True)
@@ -236,10 +242,12 @@ class User(AbstractBaseUser, PermissionsMixin):
     sync_unread_private_threads = models.BooleanField(default=False)
 
     subscribe_to_started_threads = models.PositiveIntegerField(
-        default=SUBSCRIBE_NONE, choices=SUBSCRIBE_CHOICES
+        default=SUBSCRIBE_NONE,
+        choices=SUBSCRIBE_CHOICES,
     )
     subscribe_to_replied_threads = models.PositiveIntegerField(
-        default=SUBSCRIBE_NONE, choices=SUBSCRIBE_CHOICES
+        default=SUBSCRIBE_NONE,
+        choices=SUBSCRIBE_CHOICES,
     )
 
     threads = models.PositiveIntegerField(default=0)

+ 12 - 15
misago/users/permissions/delete.py

@@ -28,13 +28,13 @@ class PermissionsForm(forms.Form):
         label=_("Maximum age of deleted account (in days)"),
         help_text=_("Enter zero to disable this check."),
         min_value=0,
-        initial=0
+        initial=0,
     )
     can_delete_users_with_less_posts_than = forms.IntegerField(
         label=_("Maximum number of posts on deleted account"),
         help_text=_("Enter zero to disable this check."),
         min_value=0,
-        initial=0
+        initial=0,
     )
 
 
@@ -62,7 +62,7 @@ def build_acl(acl, roles, key_name):
         roles=roles,
         key=key_name,
         can_delete_users_newer_than=algebra.greater,
-        can_delete_users_with_less_posts_than=algebra.greater
+        can_delete_users_with_less_posts_than=algebra.greater,
     )
 
 
@@ -100,22 +100,19 @@ def allow_delete_user(user, target):
     if newer_than:
         if target.joined_on < timezone.now() - timedelta(days=newer_than):
             message = ungettext(
-                "You can't delete users that are "
-                "members for more than %(days)s day.", "You can't delete users that are "
-                "members for more than %(days)s days.", newer_than
-            ) % {
-                'days': newer_than
-            }
-            raise PermissionDenied(message)
+                "You can't delete users that are members for more than %(days)s day.",
+                "You can't delete users that are members for more than %(days)s days.",
+                newer_than,
+            )
+            raise PermissionDenied(message % {'days': newer_than})
     if less_posts_than:
         if target.posts > less_posts_than:
             message = ungettext(
                 "You can't delete users that made more than %(posts)s post.",
-                "You can't delete users that made more than %(posts)s posts.", less_posts_than
-            ) % {
-                'posts': less_posts_than
-            }
-            raise PermissionDenied(message)
+                "You can't delete users that made more than %(posts)s posts.",
+                less_posts_than,
+            )
+            raise PermissionDenied(message % {'posts': less_posts_than})
 
 
 can_delete_user = return_boolean(allow_delete_user)

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

@@ -42,14 +42,14 @@ class PermissionsForm(forms.Form):
         label=_("Max length, in days, of imposed ban"),
         help_text=_("Enter zero to let moderators impose permanent bans."),
         min_value=0,
-        initial=0
+        initial=0,
     )
     can_lift_bans = YesNoSwitch(label=_("Can lift bans"))
     max_lifted_ban_length = forms.IntegerField(
         label=_("Max length, in days, of lifted ban"),
         help_text=_("Enter zero to let moderators lift permanent bans."),
         min_value=0,
-        initial=0
+        initial=0,
     )
 
 
@@ -87,7 +87,7 @@ def build_acl(acl, roles, key_name):
         can_ban_users=algebra.greater,
         max_ban_length=algebra.greater_or_zero,
         can_lift_bans=algebra.greater,
-        max_lifted_ban_length=algebra.greater_or_zero
+        max_lifted_ban_length=algebra.greater_or_zero,
     )
 
 

+ 1 - 1
misago/users/permissions/profiles.py

@@ -95,7 +95,7 @@ def build_acl(acl, roles, key_name):
         can_see_ban_details=algebra.greater,
         can_see_users_emails=algebra.greater,
         can_see_users_ips=algebra.greater,
-        can_see_hidden_users=algebra.greater
+        can_see_hidden_users=algebra.greater,
     )
 
 

+ 4 - 1
misago/users/serializers/ban.py

@@ -14,7 +14,10 @@ __all__ = [
 
 def serialize_message(message):
     if message:
-        return {'plain': message, 'html': format_plaintext_for_html(message)}
+        return {
+            'plain': message,
+            'html': format_plaintext_for_html(message),
+        }
     else:
         return None
 

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

@@ -28,8 +28,10 @@ class ModerateSignatureSerializer(serializers.ModelSerializer):
     class Meta:
         model = UserModel
         fields = [
-            'signature', 'is_signature_locked', 'signature_lock_user_message',
-            'signature_lock_staff_message'
+            'signature',
+            'is_signature_locked',
+            'signature_lock_user_message',
+            'signature_lock_staff_message',
         ]
 
     def validate_signature(self, value):
@@ -38,7 +40,8 @@ class ModerateSignatureSerializer(serializers.ModelSerializer):
             raise serializers.ValidationError(
                 ungettext(
                     "Signature can't be longer than %(limit)s character.",
-                    "Signature can't be longer than %(limit)s characters.", length_limit
+                    "Signature can't be longer than %(limit)s characters.",
+                    length_limit,
                 ) % {'limit': length_limit}
             )
 

+ 11 - 2
misago/users/serializers/user.py

@@ -113,6 +113,15 @@ class UserSerializer(serializers.ModelSerializer, MutableFields):
 
 
 UserCardSerializer = UserSerializer.subset_fields(
-    'id', 'username', 'joined_on', 'rank', 'title', 'avatars', 'followers', 'threads', 'posts',
-    'status', 'absolute_url'
+    'id',
+    'username',
+    'joined_on',
+    'rank',
+    'title',
+    'avatars',
+    'followers',
+    'threads',
+    'posts',
+    'status',
+    'absolute_url',
 )

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

@@ -18,5 +18,5 @@ class UsernameChangeSerializer(serializers.ModelSerializer):
         model = UsernameChange
         fields = (
             'id', 'user', 'changed_by', 'changed_by_username', 'changed_on', 'new_username',
-            'old_username'
+            'old_username',
         )

+ 1 - 3
misago/users/signals.py

@@ -3,11 +3,9 @@ from django.dispatch import Signal, receiver
 
 delete_user_content = Signal()
 username_changed = Signal()
-"""
-Signal handlers
-"""
 
 
+# Signal handlers
 @receiver(username_changed)
 def handle_name_change(sender, **kwargs):
     sender.user_renames.update(changed_by_username=sender.username)

+ 104 - 27
misago/users/tests/test_auth_api.py

@@ -12,7 +12,12 @@ UserModel = get_user_model()
 class GatewayTests(TestCase):
     def test_api_invalid_credentials(self):
         """login api returns 400 on invalid POST"""
-        response = self.client.post('/api/auth/', data={'username': 'nope', 'password': 'nope'})
+        response = self.client.post(
+            '/api/auth/', data={
+                'username': 'nope',
+                'password': 'nope',
+            }
+        )
 
         self.assertContains(response, "Login or password is incorrect.", status_code=400)
 
@@ -27,10 +32,11 @@ class GatewayTests(TestCase):
         user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
 
         self.assertEqual(response.status_code, 200)
@@ -58,10 +64,11 @@ class GatewayTests(TestCase):
         )
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
         self.assertEqual(response.status_code, 400)
 
@@ -92,10 +99,11 @@ class GatewayTests(TestCase):
         )
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -111,10 +119,11 @@ class GatewayTests(TestCase):
         UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123', requires_activation=1)
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
         self.assertEqual(response.status_code, 400)
 
@@ -132,10 +141,11 @@ class GatewayTests(TestCase):
         UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123', requires_activation=2)
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
         self.assertEqual(response.status_code, 400)
 
@@ -156,10 +166,11 @@ class GatewayTests(TestCase):
         user.save()
 
         response = self.client.post(
-            '/api/auth/', data={
+            '/api/auth/',
+            data={
                 'username': 'Bob',
                 'password': 'Pass.123',
-            }
+            },
         )
         self.assertContains(response, "Login or password is incorrect.", status_code=400)
 
@@ -187,7 +198,12 @@ class SendActivationAPITests(TestCase):
 
     def test_submit_valid(self):
         """request activation link api sends reset link mail"""
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertIn('Activate Bob', mail.outbox[0].subject)
@@ -200,7 +216,12 @@ class SendActivationAPITests(TestCase):
             user_message='Nope!',
         )
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertIn('Activate Bob', mail.outbox[0].subject)
@@ -210,7 +231,12 @@ class SendActivationAPITests(TestCase):
         self.user.is_active = False
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'not_found', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -224,7 +250,12 @@ class SendActivationAPITests(TestCase):
 
     def test_submit_invalid(self):
         """request activation link api errors for invalid email"""
-        response = self.client.post(self.link, data={'email': 'fake@mail.com'})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': 'fake@mail.com',
+            },
+        )
         self.assertContains(response, 'not_found', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -234,7 +265,12 @@ class SendActivationAPITests(TestCase):
         self.user.requires_activation = 0
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'Bob, your account is already active.', status_code=400)
 
     def test_submit_inactive_user(self):
@@ -242,7 +278,12 @@ class SendActivationAPITests(TestCase):
         self.user.requires_activation = 2
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'inactive_admin', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -251,7 +292,11 @@ class SendActivationAPITests(TestCase):
         self.user.requires_activation = 1
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link, data={
+                'email': self.user.email,
+            }
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertTrue(mail.outbox)
@@ -265,7 +310,12 @@ class SendPasswordFormAPITests(TestCase):
 
     def test_submit_valid(self):
         """request change password form link api sends reset link mail"""
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertIn('Change Bob password', mail.outbox[0].subject)
@@ -278,7 +328,12 @@ class SendPasswordFormAPITests(TestCase):
             user_message='Nope!',
         )
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertIn('Change Bob password', mail.outbox[0].subject)
@@ -288,7 +343,12 @@ class SendPasswordFormAPITests(TestCase):
         self.user.is_active = False
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'not_found', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -302,7 +362,12 @@ class SendPasswordFormAPITests(TestCase):
 
     def test_submit_invalid(self):
         """request change password form link api errors for invalid email"""
-        response = self.client.post(self.link, data={'email': 'fake@mail.com'})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': 'fake@mail.com',
+            },
+        )
         self.assertContains(response, 'not_found', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -312,13 +377,23 @@ class SendPasswordFormAPITests(TestCase):
         self.user.requires_activation = 1
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'inactive_user', status_code=400)
 
         self.user.requires_activation = 2
         self.user.save()
 
-        response = self.client.post(self.link, data={'email': self.user.email})
+        response = self.client.post(
+            self.link,
+            data={
+                'email': self.user.email,
+            },
+        )
         self.assertContains(response, 'inactive_admin', status_code=400)
 
         self.assertTrue(not mail.outbox)
@@ -334,7 +409,9 @@ class ChangePasswordAPITests(TestCase):
         """submit change password form api changes password"""
         response = self.client.post(
             self.link % (self.user.pk, make_password_change_token(self.user)),
-            data={'password': 'n3wp4ss!'}
+            data={
+                'password': 'n3wp4ss!',
+            },
         )
         self.assertEqual(response.status_code, 200)
 

+ 20 - 5
misago/users/tests/test_auth_backend.py

@@ -16,25 +16,37 @@ class MisagoBackendTests(TestCase):
 
     def test_authenticate_username(self):
         """auth authenticates with username"""
-        user = backend.authenticate(username=self.user.username, password=self.password)
+        user = backend.authenticate(
+            username=self.user.username,
+            password=self.password,
+        )
 
         self.assertEqual(user, self.user)
 
     def test_authenticate_email(self):
         """auth authenticates with email instead of username"""
-        user = backend.authenticate(username=self.user.email, password=self.password)
+        user = backend.authenticate(
+            username=self.user.email,
+            password=self.password,
+        )
 
         self.assertEqual(user, self.user)
 
     def test_authenticate_invalid_credential(self):
         """auth handles invalid credentials"""
-        user = backend.authenticate(username='InvalidCredential', password=self.password)
+        user = backend.authenticate(
+            username='InvalidCredential',
+            password=self.password,
+        )
 
         self.assertIsNone(user)
 
     def test_authenticate_invalid_password(self):
         """auth validates password"""
-        user = backend.authenticate(username=self.user.email, password='Invalid')
+        user = backend.authenticate(
+            username=self.user.email,
+            password='Invalid',
+        )
 
         self.assertIsNone(user)
 
@@ -43,7 +55,10 @@ class MisagoBackendTests(TestCase):
         self.user.is_active = False
         self.user.save()
 
-        user = backend.authenticate(username=self.user.email, password=self.password)
+        user = backend.authenticate(
+            username=self.user.email,
+            password=self.password,
+        )
 
         self.assertIsNone(user)
 

+ 17 - 3
misago/users/tests/test_auth_views.py

@@ -20,14 +20,22 @@ class AuthViewsTests(TestCase):
     def test_login_view_redirect_to(self):
         """login view respects redirect_to POST"""
         # valid redirect
-        response = self.client.post(reverse('misago:login'), data={'redirect_to': '/redirect/'})
+        response = self.client.post(
+            reverse('misago:login'),
+            data={
+                'redirect_to': '/redirect/',
+            },
+        )
 
         self.assertEqual(response.status_code, 302)
         self.assertEqual(response['location'], '/redirect/')
 
         # invalid redirect (redirects to other site)
         response = self.client.post(
-            reverse('misago:login'), data={'redirect_to': 'http://somewhereelse.com/page.html'}
+            reverse('misago:login'),
+            data={
+                'redirect_to': 'http://somewhereelse.com/page.html',
+            },
         )
 
         self.assertEqual(response.status_code, 302)
@@ -35,7 +43,13 @@ class AuthViewsTests(TestCase):
 
     def test_logout_view(self):
         """logout view logs user out on post"""
-        response = self.client.post('/api/auth/', data={'username': 'nope', 'password': 'nope'})
+        response = self.client.post(
+            '/api/auth/',
+            data={
+                'username': 'nope',
+                'password': 'nope',
+            },
+        )
 
         self.assertContains(response, "Login or password is incorrect.", status_code=400)
 

+ 2 - 1
misago/users/tests/test_avatars.py

@@ -84,7 +84,7 @@ class AvatarsStoreTests(TestCase):
 
 class AvatarSetterTests(TestCase):
     def setUp(self):
-        self.user = UserModel.objects.create_user('Bob', 'kontakt@rpiton.com', 'pass123')
+        self.user = UserModel.objects.create_user('Bob', 'kontakt@example.com', 'pass123')
 
         self.user.avatars = None
         self.user.save()
@@ -158,6 +158,7 @@ class AvatarSetterTests(TestCase):
         gibberish_email = '%s@%s.%s' % (
             get_random_string(6), get_random_string(6), get_random_string(3)
         )
+
         self.user.set_email(gibberish_email)
         self.user.save()
 

+ 12 - 9
misago/users/tests/test_avatarserver_views.py

@@ -15,15 +15,15 @@ class AvatarServerTests(TestCase):
         self.user.avatars = [
             {
                 'size': 200,
-                'url': '/media/avatars/avatar-200.png'
+                'url': '/media/avatars/avatar-200.png',
             },
             {
                 'size': 100,
-                'url': '/media/avatars/avatar-100.png'
+                'url': '/media/avatars/avatar-100.png',
             },
             {
                 'size': 50,
-                'url': '/media/avatars/avatar-50.png'
+                'url': '/media/avatars/avatar-50.png',
             },
         ]
 
@@ -32,10 +32,11 @@ class AvatarServerTests(TestCase):
     def test_get_user_avatar_exact_size(self):
         """avatar server resolved valid avatar url for user"""
         avatar_url = reverse(
-            'misago:user-avatar', kwargs={
+            'misago:user-avatar',
+            kwargs={
                 'pk': self.user.pk,
                 'size': 100,
-            }
+            },
         )
 
         response = self.client.get(avatar_url)
@@ -46,10 +47,11 @@ class AvatarServerTests(TestCase):
     def test_get_user_avatar_inexact_size(self):
         """avatar server resolved valid avatar fallback for user"""
         avatar_url = reverse(
-            'misago:user-avatar', kwargs={
+            'misago:user-avatar',
+            kwargs={
                 'pk': self.user.pk,
                 'size': 150,
-            }
+            },
         )
 
         response = self.client.get(avatar_url)
@@ -60,10 +62,11 @@ class AvatarServerTests(TestCase):
     def test_get_notfound_user_avatar(self):
         """avatar server handles deleted user avatar requests"""
         avatar_url = reverse(
-            'misago:user-avatar', kwargs={
+            'misago:user-avatar',
+            kwargs={
                 'pk': self.user.pk + 1,
                 'size': 150,
-            }
+            },
         )
         response = self.client.get(avatar_url)
 

+ 21 - 9
misago/users/tests/test_banadmin_views.py

@@ -35,7 +35,7 @@ class BanAdminViewsTests(AdminTestCase):
                     'user_message': 'Lorem ipsum dolor met',
                     'staff_message': 'Sit amet elit',
                     'expires_on': test_date.isoformat(),
-                }
+                },
             )
             self.assertEqual(response.status_code, 302)
 
@@ -47,8 +47,10 @@ class BanAdminViewsTests(AdminTestCase):
 
         response = self.client.post(
             reverse('misago:admin:users:bans:index'),
-            data={'action': 'delete',
-                  'selected_items': bans_pks}
+            data={
+                'action': 'delete',
+                'selected_items': bans_pks,
+            },
         )
         self.assertEqual(response.status_code, 302)
         self.assertEqual(Ban.objects.count(), 0)
@@ -68,7 +70,7 @@ class BanAdminViewsTests(AdminTestCase):
                 'user_message': 'Lorem ipsum dolor met',
                 'staff_message': 'Sit amet elit',
                 'expires_on': test_date.isoformat(),
-            }
+            },
         )
         self.assertEqual(response.status_code, 302)
 
@@ -84,11 +86,16 @@ class BanAdminViewsTests(AdminTestCase):
             data={
                 'check_type': '0',
                 'banned_value': 'Admin',
-            }
+            },
         )
 
         test_ban = Ban.objects.get(banned_value='admin')
-        form_link = reverse('misago:admin:users:bans:edit', kwargs={'pk': test_ban.pk})
+        form_link = reverse(
+            'misago:admin:users:bans:edit',
+            kwargs={
+                'pk': test_ban.pk,
+            },
+        )
 
         response = self.client.post(
             form_link,
@@ -98,7 +105,7 @@ class BanAdminViewsTests(AdminTestCase):
                 'user_message': 'Lorem ipsum dolor met',
                 'staff_message': 'Sit amet elit',
                 'expires_on': '',
-            }
+            },
         )
         self.assertEqual(response.status_code, 302)
 
@@ -114,13 +121,18 @@ class BanAdminViewsTests(AdminTestCase):
             data={
                 'check_type': '0',
                 'banned_value': 'TestBan',
-            }
+            },
         )
 
         test_ban = Ban.objects.get(banned_value='testban')
 
         response = self.client.post(
-            reverse('misago:admin:users:bans:delete', kwargs={'pk': test_ban.pk})
+            reverse(
+                'misago:admin:users:bans:delete',
+                kwargs={
+                    'pk': test_ban.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 

+ 41 - 16
misago/users/tests/test_bans.py

@@ -18,18 +18,25 @@ class GetBanTests(TestCase):
         nonexistent_ban = get_username_ban('nonexistent')
         self.assertIsNone(nonexistent_ban)
 
-        Ban.objects.create(banned_value='expired', expires_on=timezone.now() - timedelta(days=7))
+        Ban.objects.create(
+            banned_value='expired',
+            expires_on=timezone.now() - timedelta(days=7),
+        )
 
         expired_ban = get_username_ban('expired')
         self.assertIsNone(expired_ban)
 
-        Ban.objects.create(banned_value='wrongtype', check_type=Ban.EMAIL)
+        Ban.objects.create(
+            banned_value='wrongtype',
+            check_type=Ban.EMAIL,
+        )
 
         wrong_type_ban = get_username_ban('wrongtype')
         self.assertIsNone(wrong_type_ban)
 
         valid_ban = Ban.objects.create(
-            banned_value='admi*', expires_on=timezone.now() + timedelta(days=7)
+            banned_value='admi*',
+            expires_on=timezone.now() + timedelta(days=7),
         )
         self.assertEqual(get_username_ban('admiral').pk, valid_ban.pk)
 
@@ -41,13 +48,16 @@ class GetBanTests(TestCase):
         Ban.objects.create(
             banned_value='ex@pired.com',
             check_type=Ban.EMAIL,
-            expires_on=timezone.now() - timedelta(days=7)
+            expires_on=timezone.now() - timedelta(days=7),
         )
 
         expired_ban = get_email_ban('ex@pired.com')
         self.assertIsNone(expired_ban)
 
-        Ban.objects.create(banned_value='wrong@type.com', check_type=Ban.IP)
+        Ban.objects.create(
+            banned_value='wrong@type.com',
+            check_type=Ban.IP,
+        )
 
         wrong_type_ban = get_email_ban('wrong@type.com')
         self.assertIsNone(wrong_type_ban)
@@ -55,7 +65,7 @@ class GetBanTests(TestCase):
         valid_ban = Ban.objects.create(
             banned_value='*.ru',
             check_type=Ban.EMAIL,
-            expires_on=timezone.now() + timedelta(days=7)
+            expires_on=timezone.now() + timedelta(days=7),
         )
         self.assertEqual(get_email_ban('banned@mail.ru').pk, valid_ban.pk)
 
@@ -67,13 +77,16 @@ class GetBanTests(TestCase):
         Ban.objects.create(
             banned_value='124.0.0.1',
             check_type=Ban.IP,
-            expires_on=timezone.now() - timedelta(days=7)
+            expires_on=timezone.now() - timedelta(days=7),
         )
 
         expired_ban = get_ip_ban('124.0.0.1')
         self.assertIsNone(expired_ban)
 
-        Ban.objects.create(banned_value='wrongtype', check_type=Ban.EMAIL)
+        Ban.objects.create(
+            banned_value='wrongtype',
+            check_type=Ban.EMAIL,
+        )
 
         wrong_type_ban = get_ip_ban('wrongtype')
         self.assertIsNone(wrong_type_ban)
@@ -81,7 +94,7 @@ class GetBanTests(TestCase):
         valid_ban = Ban.objects.create(
             banned_value='125.0.0.*',
             check_type=Ban.IP,
-            expires_on=timezone.now() + timedelta(days=7)
+            expires_on=timezone.now() + timedelta(days=7),
         )
         self.assertEqual(get_ip_ban('125.0.0.1').pk, valid_ban.pk)
 
@@ -98,7 +111,9 @@ class UserBansTests(TestCase):
     def test_permanent_ban(self):
         """user is caught by permanent ban"""
         Ban.objects.create(
-            banned_value='bob', user_message='User reason', staff_message='Staff reason'
+            banned_value='bob',
+            user_message='User reason',
+            staff_message='Staff reason',
         )
 
         user_ban = get_user_ban(self.user)
@@ -113,7 +128,7 @@ class UserBansTests(TestCase):
             banned_value='bo*',
             user_message='User reason',
             staff_message='Staff reason',
-            expires_on=timezone.now() + timedelta(days=7)
+            expires_on=timezone.now() + timedelta(days=7),
         )
 
         user_ban = get_user_ban(self.user)
@@ -124,14 +139,20 @@ class UserBansTests(TestCase):
 
     def test_expired_ban(self):
         """user is not caught by expired ban"""
-        Ban.objects.create(banned_value='bo*', expires_on=timezone.now() - timedelta(days=7))
+        Ban.objects.create(
+            banned_value='bo*',
+            expires_on=timezone.now() - timedelta(days=7),
+        )
 
         self.assertIsNone(get_user_ban(self.user))
         self.assertFalse(self.user.ban_cache.is_banned)
 
     def test_expired_non_flagged_ban(self):
         """user is not caught by expired but checked ban"""
-        Ban.objects.create(banned_value='bo*', expires_on=timezone.now() - timedelta(days=7))
+        Ban.objects.create(
+            banned_value='bo*',
+            expires_on=timezone.now() - timedelta(days=7),
+        )
         Ban.objects.update(is_checked=True)
 
         self.assertIsNone(get_user_ban(self.user))
@@ -152,7 +173,11 @@ class RequestIPBansTests(TestCase):
 
     def test_permanent_ban(self):
         """ip is caught by permanent ban"""
-        Ban.objects.create(check_type=Ban.IP, banned_value='127.0.0.1', user_message='User reason')
+        Ban.objects.create(
+            check_type=Ban.IP,
+            banned_value='127.0.0.1',
+            user_message='User reason',
+        )
 
         ip_ban = get_request_ip_ban(FakeRequest())
         self.assertTrue(ip_ban['is_banned'])
@@ -168,7 +193,7 @@ class RequestIPBansTests(TestCase):
             check_type=Ban.IP,
             banned_value='127.0.0.1',
             user_message='User reason',
-            expires_on=timezone.now() + timedelta(days=7)
+            expires_on=timezone.now() + timedelta(days=7),
         )
 
         ip_ban = get_request_ip_ban(FakeRequest())
@@ -185,7 +210,7 @@ class RequestIPBansTests(TestCase):
             check_type=Ban.IP,
             banned_value='127.0.0.1',
             user_message='User reason',
-            expires_on=timezone.now() - timedelta(days=7)
+            expires_on=timezone.now() - timedelta(days=7),
         )
 
         ip_ban = get_request_ip_ban(FakeRequest())

+ 1 - 2
misago/users/tests/test_commands.py

@@ -9,12 +9,11 @@ UserModel = get_user_model()
 class CreateSuperUserTests(TestCase):
     def test_createsuperuser(self):
         """createsuperuser creates user account in perfect conditions"""
-
         opts = {
             'username': 'Boberson',
             'email': 'bob@test.com',
             'password': 'Pass.123',
-            'verbosity': 0
+            'verbosity': 0,
         }
 
         call_command('createsuperuser', **opts)

+ 3 - 2
misago/users/tests/test_createsuperuser.py

@@ -11,20 +11,21 @@ class CreateSuperuserTests(TestCase):
     def test_create_superuser(self):
         """command creates superuser"""
         out = StringIO()
+
         call_command(
             "createsuperuser",
             interactive=False,
             username="joe",
             email="joe@somewhere.org",
             password="Pass.123",
-            stdout=out
+            stdout=out,
         )
 
         new_user = UserModel.objects.order_by('-id')[:1][0]
 
         self.assertEqual(
             out.getvalue().splitlines()[-1].strip(),
-            'Superuser #%s has been created successfully.' % new_user.pk
+            'Superuser #%s has been created successfully.' % new_user.pk,
         )
 
         self.assertEqual(new_user.username, 'joe')

+ 10 - 2
misago/users/tests/test_decorators.py

@@ -36,14 +36,22 @@ class DenyGuestsTests(UserTestCase):
 class DenyBannedIPTests(UserTestCase):
     def test_success(self):
         """deny_banned_ips decorator allowed unbanned request"""
-        Ban.objects.create(check_type=Ban.IP, banned_value='83.*', user_message="Ya got banned!")
+        Ban.objects.create(
+            check_type=Ban.IP,
+            banned_value='83.*',
+            user_message="Ya got banned!",
+        )
 
         response = self.client.post(reverse('misago:request-activation'))
         self.assertEqual(response.status_code, 200)
 
     def test_fail(self):
         """deny_banned_ips decorator denied banned request"""
-        Ban.objects.create(check_type=Ban.IP, banned_value='127.*', user_message="Ya got banned!")
+        Ban.objects.create(
+            check_type=Ban.IP,
+            banned_value='127.*',
+            user_message="Ya got banned!",
+        )
 
         response = self.client.post(reverse('misago:request-activation'))
         self.assertContains(response, encode_json_html("<p>Ya got banned!</p>"), status_code=403)

+ 1 - 1
misago/users/tests/test_djangoadmin_auth.py

@@ -22,7 +22,7 @@ class DjangoAdminAuthTests(AdminTestCase):
             data={
                 'username': self.user.email,
                 'password': self.USER_PASSWORD,
-            }
+            },
         )
         self.assertEqual(response.status_code, 302)
 

+ 4 - 4
misago/users/tests/test_forgottenpassword_views.py

@@ -41,7 +41,7 @@ class ForgottenPasswordViewsTests(UserTestCase):
                 kwargs={
                     'pk': test_user.pk,
                     'token': password_token,
-                }
+                },
             )
         )
         self.assertContains(response, encode_json_html("<p>Nope!</p>"), status_code=403)
@@ -60,7 +60,7 @@ class ForgottenPasswordViewsTests(UserTestCase):
                 kwargs={
                     'pk': test_user.pk,
                     'token': password_token,
-                }
+                },
             )
         )
         self.assertContains(response, 'your link has expired', status_code=400)
@@ -75,7 +75,7 @@ class ForgottenPasswordViewsTests(UserTestCase):
                 kwargs={
                     'pk': test_user.pk,
                     'token': 'abcdfghqsads',
-                }
+                },
             )
         )
         self.assertContains(response, 'your link is invalid', status_code=400)
@@ -92,7 +92,7 @@ class ForgottenPasswordViewsTests(UserTestCase):
                 kwargs={
                     'pk': test_user.pk,
                     'token': password_token,
-                }
+                },
             )
         )
         self.assertContains(response, password_token)

+ 4 - 1
misago/users/tests/test_invalidatebans.py

@@ -58,7 +58,10 @@ class InvalidateBansTests(TestCase):
 
         # expire bans
         expired_date = (timezone.now() - timedelta(days=10))
-        Ban.objects.all().update(expires_on=expired_date, is_checked=True)
+        Ban.objects.all().update(
+            expires_on=expired_date,
+            is_checked=True,
+        )
         BanCache.objects.all().update(expires_on=expired_date)
 
         # invalidate expired ban cache

+ 30 - 6
misago/users/tests/test_lists_views.py

@@ -56,7 +56,10 @@ class ActivePostersTests(UsersListTestCase):
         # Create 50 test users and see if errors appeared
         for i in range(50):
             user = UserModel.objects.create_user(
-                'Bob%s' % i, 'm%s@te.com' % i, 'Pass.123', posts=12345
+                'Bob%s' % i,
+                'm%s@te.com' % i,
+                'Pass.123',
+                posts=12345,
             )
             post_thread(category, poster=user)
 
@@ -75,7 +78,12 @@ class UsersRankTests(UsersListTestCase):
             rank_user.rank = rank
             rank_user.save()
 
-            rank_link = reverse('misago:users-rank', kwargs={'slug': rank.slug})
+            rank_link = reverse(
+                'misago:users-rank',
+                kwargs={
+                    'slug': rank.slug,
+                },
+            )
             response = self.client.get(rank_link)
 
             if rank.is_tab:
@@ -87,14 +95,22 @@ class UsersRankTests(UsersListTestCase):
     def test_disabled_users(self):
         """ranks lists excludes disabled accounts"""
         rank_user = UserModel.objects.create_user(
-            'Visible', 'visible@te.com', 'Pass.123', is_active=False
+            'Visible',
+            'visible@te.com',
+            'Pass.123',
+            is_active=False,
         )
 
         for rank in Rank.objects.iterator():
             rank_user.rank = rank
             rank_user.save()
 
-            rank_link = reverse('misago:users-rank', kwargs={'slug': rank.slug})
+            rank_link = reverse(
+                'misago:users-rank',
+                kwargs={
+                    'slug': rank.slug,
+                },
+            )
             response = self.client.get(rank_link)
 
             if rank.is_tab:
@@ -109,14 +125,22 @@ class UsersRankTests(UsersListTestCase):
         self.user.save()
 
         rank_user = UserModel.objects.create_user(
-            'Visible', 'visible@te.com', 'Pass.123', is_active=False
+            'Visible',
+            'visible@te.com',
+            'Pass.123',
+            is_active=False,
         )
 
         for rank in Rank.objects.iterator():
             rank_user.rank = rank
             rank_user.save()
 
-            rank_link = reverse('misago:users-rank', kwargs={'slug': rank.slug})
+            rank_link = reverse(
+                'misago:users-rank',
+                kwargs={
+                    'slug': rank.slug,
+                },
+            )
             response = self.client.get(rank_link)
 
             if rank.is_tab:

+ 23 - 5
misago/users/tests/test_options_views.py

@@ -13,7 +13,12 @@ class OptionsViewsTests(AuthenticatedUserTestCase):
     def test_form_view_returns_200(self):
         """/options/some-form has no show stoppers"""
         response = self.client.get(
-            reverse('misago:options-form', kwargs={'form_name': 'some-fake-form'})
+            reverse(
+                'misago:options-form',
+                kwargs={
+                    'form_name': 'some-fake-form',
+                },
+            )
         )
         self.assertEqual(response.status_code, 200)
 
@@ -37,7 +42,12 @@ class ConfirmChangeEmailTests(AuthenticatedUserTestCase):
     def test_invalid_token(self):
         """invalid token is rejected"""
         response = self.client.get(
-            reverse('misago:options-confirm-email-change', kwargs={'token': 'invalid'})
+            reverse(
+                'misago:options-confirm-email-change',
+                kwargs={
+                    'token': 'invalid',
+                },
+            )
         )
 
         self.assertContains(response, "Change confirmation link is invalid.", status_code=400)
@@ -58,8 +68,11 @@ class ConfirmChangePasswordTests(AuthenticatedUserTestCase):
         link = '/api/users/%s/change-password/' % self.user.pk
 
         response = self.client.post(
-            link, data={'new_password': 'n3wp4ssword',
-                        'password': self.USER_PASSWORD}
+            link,
+            data={
+                'new_password': 'n3wp4ssword',
+                'password': self.USER_PASSWORD,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -71,7 +84,12 @@ class ConfirmChangePasswordTests(AuthenticatedUserTestCase):
     def test_invalid_token(self):
         """invalid token is rejected"""
         response = self.client.get(
-            reverse('misago:options-confirm-password-change', kwargs={'token': 'invalid'})
+            reverse(
+                'misago:options-confirm-password-change',
+                kwargs={
+                    'token': 'invalid',
+                },
+            )
         )
 
         self.assertContains(response, "Change confirmation link is invalid.", status_code=400)

+ 52 - 15
misago/users/tests/test_profile_views.py

@@ -19,9 +19,13 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.category = Category.objects.get(slug='first-category')
 
     def test_outdated_slugs(self):
-        """user profile view redirects to valid slig"""
-        invalid_kwargs = {'slug': 'baww', 'pk': self.user.pk}
-        response = self.client.get(reverse('misago:user-posts', kwargs=invalid_kwargs))
+        """user profile view redirects to valid slug"""
+        response = self.client.get(
+            reverse('misago:user-posts', kwargs={
+                'slug': 'baww',
+                'pk': self.user.pk,
+            })
+        )
 
         self.assertEqual(response.status_code, 301)
 
@@ -56,7 +60,10 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "You have posted no messages")
 
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)
@@ -78,7 +85,10 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "You have no started threads.")
 
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
 
         response = self.client.get(link)
         self.assertEqual(response.status_code, 200)
@@ -94,7 +104,10 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
 
     def test_user_followers(self):
         """user profile followers list has no showstoppers"""
-        response = self.client.get(reverse('misago:user-followers', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-followers',
+            kwargs=self.link_kwargs,
+        ))
 
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, 'You have no followers.')
@@ -105,14 +118,20 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
             followers.append(UserModel.objects.create_user(*user_data))
             self.user.followed_by.add(followers[-1])
 
-        response = self.client.get(reverse('misago:user-followers', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-followers',
+            kwargs=self.link_kwargs,
+        ))
         self.assertEqual(response.status_code, 200)
         for i in range(10):
             self.assertContains(response, "Follower%s" % i)
 
     def test_user_follows(self):
         """user profile follows list has no showstoppers"""
-        response = self.client.get(reverse('misago:user-follows', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-follows',
+            kwargs=self.link_kwargs,
+        ))
 
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, 'You are not following any users.')
@@ -123,14 +142,20 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
             followers.append(UserModel.objects.create_user(*user_data))
             followers[-1].followed_by.add(self.user)
 
-        response = self.client.get(reverse('misago:user-follows', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-follows',
+            kwargs=self.link_kwargs,
+        ))
         self.assertEqual(response.status_code, 200)
         for i in range(10):
             self.assertContains(response, "Follower%s" % i)
 
     def test_username_history_list(self):
         """user name changes history list has no showstoppers"""
-        response = self.client.get(reverse('misago:username-history', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:username-history',
+            kwargs=self.link_kwargs,
+        ))
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, 'Your username was never changed.')
 
@@ -139,7 +164,10 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         self.user.set_username('TestUser')
         self.user.save()
 
-        response = self.client.get(reverse('misago:username-history', kwargs=self.link_kwargs))
+        response = self.client.get(reverse(
+            'misago:username-history',
+            kwargs=self.link_kwargs,
+        ))
 
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "TestUser")
@@ -154,14 +182,20 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
         test_user = UserModel.objects.create_user("Bob", "bob@bob.com", 'pass.123')
         link_kwargs = {'slug': test_user.slug, 'pk': test_user.pk}
 
-        response = self.client.get(reverse('misago:user-ban', kwargs=link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-ban',
+            kwargs=link_kwargs,
+        ))
         self.assertEqual(response.status_code, 404)
 
         override_acl(self.user, {
             'can_see_ban_details': 1,
         })
 
-        response = self.client.get(reverse('misago:user-ban', kwargs=link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-ban',
+            kwargs=link_kwargs,
+        ))
         self.assertEqual(response.status_code, 404)
 
         override_acl(self.user, {
@@ -173,10 +207,13 @@ class UserProfileViewsTests(AuthenticatedUserTestCase):
             banned_value=test_user.username,
             user_message="User m3ss4ge.",
             staff_message="Staff m3ss4ge.",
-            is_checked=True
+            is_checked=True,
         )
 
-        response = self.client.get(reverse('misago:user-ban', kwargs=link_kwargs))
+        response = self.client.get(reverse(
+            'misago:user-ban',
+            kwargs=link_kwargs,
+        ))
 
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, 'User m3ss4ge')

+ 63 - 18
misago/users/tests/test_rankadmin_views.py

@@ -38,7 +38,7 @@ class RankAdminViewsTests(AdminTestCase):
                 'style': 'test',
                 'is_tab': '1',
                 'roles': [test_role_a.pk, test_role_c.pk],
-            }
+            },
         )
         self.assertEqual(response.status_code, 302)
 
@@ -67,24 +67,34 @@ class RankAdminViewsTests(AdminTestCase):
                 'style': 'test',
                 'is_tab': '1',
                 'roles': [test_role_a.pk, test_role_c.pk],
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.get(
-            reverse('misago:admin:users:ranks:edit', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:edit',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, test_rank.name)
         self.assertContains(response, test_rank.title)
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:edit', kwargs={'pk': test_rank.pk}),
+            reverse(
+                'misago:admin:users:ranks:edit',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            ),
             data={
                 'name': 'Top Lel',
                 'roles': [test_role_b.pk],
-            }
+            },
         )
         self.assertEqual(response.status_code, 302)
 
@@ -109,13 +119,18 @@ class RankAdminViewsTests(AdminTestCase):
                 'title': 'Test Title',
                 'style': 'test',
                 'is_tab': '1',
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:default', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:default',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 
@@ -132,13 +147,18 @@ class RankAdminViewsTests(AdminTestCase):
                 'title': 'Test Title',
                 'style': 'test',
                 'is_tab': '1',
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:up', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:up',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 
@@ -155,18 +175,28 @@ class RankAdminViewsTests(AdminTestCase):
                 'title': 'Test Title',
                 'style': 'test',
                 'is_tab': '1',
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         # Move rank up
         response = self.client.post(
-            reverse('misago:admin:users:ranks:up', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:up',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:down', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:down',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 
@@ -184,13 +214,18 @@ class RankAdminViewsTests(AdminTestCase):
                 'title': 'Test Title',
                 'style': 'test',
                 'is_tab': '1',
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.get(
-            reverse('misago:admin:users:ranks:users', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:users',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 
@@ -204,13 +239,18 @@ class RankAdminViewsTests(AdminTestCase):
                 'title': 'Test Title',
                 'style': 'test',
                 'is_tab': '1',
-            }
+            },
         )
 
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:delete', kwargs={'pk': test_rank.pk})
+            reverse(
+                'misago:admin:users:ranks:delete',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            )
         )
         self.assertEqual(response.status_code, 302)
 
@@ -255,11 +295,16 @@ class RankAdminViewsTests(AdminTestCase):
         test_rank = Rank.objects.get(slug='test-rank')
 
         response = self.client.post(
-            reverse('misago:admin:users:ranks:edit', kwargs={'pk': test_rank.pk}),
+            reverse(
+                'misago:admin:users:ranks:edit',
+                kwargs={
+                    'pk': test_rank.pk,
+                },
+            ),
             data={
                 'name': 'Members',
                 'roles': [test_role_a.pk],
-            }
+            },
         )
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "This name collides with other rank.")

+ 34 - 8
misago/users/tests/test_rest_permissions.py

@@ -11,7 +11,10 @@ class UnbannedOnlyTests(UserTestCase):
     def test_api_allows_guests(self):
         """policy allows guests"""
         response = self.client.post(
-            reverse('misago:api:send-password-form'), data={'email': self.user.email}
+            reverse('misago:api:send-password-form'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -20,16 +23,26 @@ class UnbannedOnlyTests(UserTestCase):
         self.login_user(self.user)
 
         response = self.client.post(
-            reverse('misago:api:send-password-form'), data={'email': self.user.email}
+            reverse('misago:api:send-password-form'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
     def test_api_blocks_banned(self):
         """policy blocked banned ip"""
-        Ban.objects.create(check_type=Ban.IP, banned_value='127.*', user_message='Ya got banned!')
+        Ban.objects.create(
+            check_type=Ban.IP,
+            banned_value='127.*',
+            user_message='Ya got banned!',
+        )
 
         response = self.client.post(
-            reverse('misago:api:send-password-form'), data={'email': self.user.email}
+            reverse('misago:api:send-password-form'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 403)
 
@@ -44,7 +57,10 @@ class UnbannedAnonOnlyTests(UserTestCase):
         self.user.save()
 
         response = self.client.post(
-            reverse('misago:api:send-activation'), data={'email': self.user.email}
+            reverse('misago:api:send-activation'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -53,15 +69,25 @@ class UnbannedAnonOnlyTests(UserTestCase):
         self.login_user(self.user)
 
         response = self.client.post(
-            reverse('misago:api:send-activation'), data={'email': self.user.email}
+            reverse('misago:api:send-activation'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 403)
 
     def test_api_blocks_banned(self):
         """policy blocked banned ip"""
-        Ban.objects.create(check_type=Ban.IP, banned_value='127.*', user_message='Ya got banned!')
+        Ban.objects.create(
+            check_type=Ban.IP,
+            banned_value='127.*',
+            user_message='Ya got banned!',
+        )
 
         response = self.client.post(
-            reverse('misago:api:send-activation'), data={'email': self.user.email}
+            reverse('misago:api:send-activation'),
+            data={
+                'email': self.user.email,
+            },
         )
         self.assertEqual(response.status_code, 403)

+ 9 - 2
misago/users/tests/test_search.py

@@ -103,7 +103,10 @@ class SearchApiTests(AuthenticatedUserTestCase):
     def test_search_disabled(self):
         """api respects disabled users visibility"""
         disabled_user = UserModel.objects.create_user(
-            'DisabledUser', 'visible@te.com', 'Pass.123', is_active=False
+            'DisabledUser',
+            'visible@te.com',
+            'Pass.123',
+            is_active=False,
         )
 
         response = self.client.get('%s?q=DisabledUser' % self.api_link)
@@ -137,4 +140,8 @@ class SearchProviderApiTests(SearchApiTests):
     def setUp(self):
         super(SearchProviderApiTests, self).setUp()
 
-        self.api_link = reverse('misago:api:search', kwargs={'search_provider': 'users'})
+        self.api_link = reverse(
+            'misago:api:search', kwargs={
+                'search_provider': 'users',
+            }
+        )

+ 36 - 21
misago/users/tests/test_user_avatar_api.py

@@ -147,10 +147,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                         'x': 0,
                         'y': 0
                     },
-                    'zoom': 1
-                }
+                    'zoom': 1,
+                },
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
 
         response_json = response.json()
@@ -172,10 +172,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                         'x': 0,
                         'y': 0
                     },
-                    'zoom': 1
-                }
+                    'zoom': 1,
+                },
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         self.assertContains(response, "This avatar type is not allowed.", status_code=400)
 
@@ -188,10 +188,10 @@ class UserAvatarTests(AuthenticatedUserTestCase):
                         'x': 0,
                         'y': 0
                     },
-                    'zoom': 1
-                }
+                    'zoom': 1,
+                },
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         self.assertContains(response, "Avatar was re-cropped.")
 
@@ -222,16 +222,20 @@ class UserAvatarTests(AuthenticatedUserTestCase):
 
         # no image id is handled
         response = self.client.post(
-            self.link, data={
+            self.link,
+            data={
                 'avatar': 'galleries',
-            }
+            },
         )
         self.assertContains(response, "Incorrect image.", status_code=400)
 
         # invalid id is handled
         response = self.client.post(
-            self.link, data={'avatar': 'galleries',
-                             'image': 'asdsadsadsa'}
+            self.link,
+            data={
+                'avatar': 'galleries',
+                'image': 'asdsadsadsa',
+            },
         )
         self.assertContains(response, "Incorrect image.", status_code=400)
 
@@ -244,8 +248,11 @@ class UserAvatarTests(AuthenticatedUserTestCase):
 
         test_avatar = options['galleries'][0]['images'][0]['id']
         response = self.client.post(
-            self.link, data={'avatar': 'galleries',
-                             'image': test_avatar + 5000}
+            self.link,
+            data={
+                'avatar': 'galleries',
+                'image': test_avatar + 5000,
+            },
         )
         self.assertContains(response, "Incorrect image.", status_code=400)
 
@@ -266,7 +273,13 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.assertTrue(options['galleries'])
 
         test_avatar = options['galleries'][0]['images'][0]['id']
-        response = self.client.post(self.link, data={'avatar': 'galleries', 'image': test_avatar})
+        response = self.client.post(
+            self.link,
+            data={
+                'avatar': 'galleries',
+                'image': test_avatar,
+            },
+        )
 
         self.assertContains(response, "Avatar from gallery was set.")
 
@@ -321,7 +334,7 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
                 'avatar_lock_user_message': "Test user message.",
                 'avatar_lock_staff_message': "Test staff message.",
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         self.assertEqual(response.status_code, 200)
 
@@ -350,7 +363,7 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
                 'avatar_lock_user_message': None,
                 'avatar_lock_staff_message': None,
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         self.assertEqual(response.status_code, 200)
 
@@ -378,7 +391,7 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
                 'avatar_lock_user_message': '',
                 'avatar_lock_staff_message': '',
             }),
-            content_type="application/json"
+            content_type="application/json",
         )
         self.assertEqual(response.status_code, 200)
 
@@ -400,9 +413,11 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'is_avatar_locked': False,
-            }), content_type="application/json"
+            }),
+            content_type="application/json",
         )
         self.assertEqual(response.status_code, 200)
 

+ 31 - 11
misago/users/tests/test_user_changeemail_api.py

@@ -37,16 +37,22 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
     def test_invalid_password(self):
         """api errors correctly for invalid password"""
         response = self.client.post(
-            self.link, data={'new_email': 'new@email.com',
-                             'password': 'Lor3mIpsum'}
+            self.link,
+            data={
+                'new_email': 'new@email.com',
+                'password': 'Lor3mIpsum',
+            },
         )
         self.assertContains(response, 'password is invalid', status_code=400)
 
     def test_invalid_input(self):
         """api errors correctly for invalid input"""
         response = self.client.post(
-            self.link, data={'new_email': '',
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_email': '',
+                'password': self.USER_PASSWORD,
+            },
         )
 
         self.assertEqual(response.status_code, 400)
@@ -55,8 +61,11 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, data={'new_email': 'newmail',
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_email': 'newmail',
+                'password': self.USER_PASSWORD,
+            },
         )
 
         self.assertEqual(response.status_code, 400)
@@ -69,8 +78,11 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
         UserModel.objects.create_user('BobBoberson', 'new@email.com', 'Pass.123')
 
         response = self.client.post(
-            self.link, data={'new_email': 'new@email.com',
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_email': 'new@email.com',
+                'password': self.USER_PASSWORD,
+            },
         )
         self.assertContains(response, 'not available', status_code=400)
 
@@ -79,8 +91,11 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
         new_email = 'new@email.com'
 
         response = self.client.post(
-            self.link, data={'new_email': new_email,
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_email': new_email,
+                'password': self.USER_PASSWORD,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -93,7 +108,12 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
             self.fail("E-mail sent didn't contain confirmation url")
 
         response = self.client.get(
-            reverse('misago:options-confirm-email-change', kwargs={'token': token})
+            reverse(
+                'misago:options-confirm-email-change',
+                kwargs={
+                    'token': token,
+                },
+            )
         )
 
         self.assertEqual(response.status_code, 200)

+ 23 - 9
misago/users/tests/test_user_changepassword_api.py

@@ -33,8 +33,11 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
     def test_invalid_password(self):
         """api errors correctly for invalid password"""
         response = self.client.post(
-            self.link, data={'new_password': 'N3wP@55w0rd',
-                             'password': 'Lor3mIpsum'}
+            self.link,
+            data={
+                'new_password': 'N3wP@55w0rd',
+                'password': 'Lor3mIpsum',
+            },
         )
 
         self.assertEqual(response.status_code, 400)
@@ -45,8 +48,11 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
     def test_blank_input(self):
         """api errors correctly for blank input"""
         response = self.client.post(
-            self.link, data={'new_password': '',
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_password': '',
+                'password': self.USER_PASSWORD,
+            },
         )
 
         self.assertEqual(response.status_code, 400)
@@ -57,8 +63,11 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
     def test_short_new_pasword(self):
         """api errors correctly for short new password"""
         response = self.client.post(
-            self.link, data={'new_password': 'n',
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_password': 'n',
+                'password': self.USER_PASSWORD,
+            },
         )
 
         self.assertEqual(response.status_code, 400)
@@ -74,8 +83,11 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
         new_password = 'N3wP@55w0rd'
 
         response = self.client.post(
-            self.link, data={'new_password': new_password,
-                             'password': self.USER_PASSWORD}
+            self.link,
+            data={
+                'new_password': new_password,
+                'password': self.USER_PASSWORD,
+            },
         )
         self.assertEqual(response.status_code, 200)
 
@@ -88,7 +100,9 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
             self.fail("E-mail sent didn't contain confirmation url")
 
         response = self.client.get(
-            reverse('misago:options-confirm-password-change', kwargs={'token': token})
+            reverse('misago:options-confirm-password-change', kwargs={
+                'token': token,
+            })
         )
 
         self.assertEqual(response.status_code, 200)

+ 40 - 23
misago/users/tests/test_user_create_api.py

@@ -46,12 +46,14 @@ class UserCreateTests(UserTestCase):
             data={
                 'username': user.username,
                 'email': 'loremipsum@dolor.met',
-                'password': 'LoremP4ssword'
-            }
+                'password': 'LoremP4ssword',
+            },
         )
 
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'username': ["This username is not available."]})
+        self.assertEqual(response.json(), {
+            'username': ["This username is not available."],
+        })
 
     def test_registration_validates_email(self):
         """api validates usernames"""
@@ -59,21 +61,27 @@ class UserCreateTests(UserTestCase):
 
         response = self.client.post(
             self.api_link,
-            data={'username': 'totallyNew',
-                  'email': user.email,
-                  'password': 'LoremP4ssword'}
+            data={
+                'username': 'totallyNew',
+                'email': user.email,
+                'password': 'LoremP4ssword',
+            },
         )
 
         self.assertEqual(response.status_code, 400)
-        self.assertEqual(response.json(), {'email': ["This e-mail address is not available."]})
+        self.assertEqual(response.json(), {
+            'email': ["This e-mail address is not available."],
+        })
 
     def test_registration_validates_password(self):
         """api uses django's validate_password to validate registrations"""
         response = self.client.post(
             self.api_link,
-            data={'username': 'Bob',
-                  'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
-                  'password': '123'}
+            data={
+                'username': 'Bob',
+                'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
+                'password': '123',
+            },
         )
 
         self.assertContains(response, "password is too short", status_code=400)
@@ -87,8 +95,8 @@ class UserCreateTests(UserTestCase):
             data={
                 'username': 'BobBoberson',
                 'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
-                'password': 'BobBoberson'
-            }
+                'password': 'BobBoberson',
+            },
         )
 
         self.assertContains(response, "password is too similar to the username", status_code=400)
@@ -100,8 +108,8 @@ class UserCreateTests(UserTestCase):
             data={
                 'username': 'Bob',
                 'email': 'l.o.r.e.m.i.p.s.u.m@gmail.com',
-                'password': 'pas123'
-            }
+                'password': 'pas123',
+            },
         )
 
         self.assertContains(response, "email is not allowed", status_code=400)
@@ -111,9 +119,12 @@ class UserCreateTests(UserTestCase):
         settings.override_setting('account_activation', 'none')
 
         response = self.client.post(
-            self.api_link, data={'username': 'Bob',
-                                 'email': 'bob@bob.com',
-                                 'password': 'pass123'}
+            self.api_link,
+            data={
+                'username': 'Bob',
+                'email': 'bob@bob.com',
+                'password': 'pass123',
+            },
         )
 
         self.assertContains(response, 'active')
@@ -135,9 +146,12 @@ class UserCreateTests(UserTestCase):
         settings.override_setting('account_activation', 'user')
 
         response = self.client.post(
-            self.api_link, data={'username': 'Bob',
-                                 'email': 'bob@bob.com',
-                                 'password': 'pass123'}
+            self.api_link,
+            data={
+                'username': 'Bob',
+                'email': 'bob@bob.com',
+                'password': 'pass123',
+            },
         )
 
         self.assertContains(response, 'user')
@@ -154,9 +168,12 @@ class UserCreateTests(UserTestCase):
         settings.override_setting('account_activation', 'admin')
 
         response = self.client.post(
-            self.api_link, data={'username': 'Bob',
-                                 'email': 'bob@bob.com',
-                                 'password': 'pass123'}
+            self.api_link,
+            data={
+                'username': 'Bob',
+                'email': 'bob@bob.com',
+                'password': 'pass123',
+            },
         )
 
         self.assertContains(response, 'admin')

+ 66 - 14
misago/users/tests/test_user_feeds_api.py

@@ -8,17 +8,29 @@ class UserThreadsApiTests(ThreadsApiTestCase):
     def setUp(self):
         super(UserThreadsApiTests, self).setUp()
 
-        self.api_link = reverse('misago:api:user-threads', kwargs={'pk': self.user.pk})
+        self.api_link = reverse(
+            'misago:api:user-threads', kwargs={
+                'pk': self.user.pk,
+            }
+        )
 
     def test_invalid_user_id(self):
         """api validates user id"""
-        link = reverse('misago:api:user-threads', kwargs={'pk': 'abcd'})
+        link = reverse(
+            'misago:api:user-threads', kwargs={
+                'pk': 'abcd',
+            }
+        )
         response = self.client.get(link)
         self.assertEqual(response.status_code, 404)
 
     def test_nonexistant_user_id(self):
         """api validates that user for id exists"""
-        link = reverse('misago:api:user-threads', kwargs={'pk': self.user.pk + 1})
+        link = reverse(
+            'misago:api:user-threads', kwargs={
+                'pk': self.user.pk + 1,
+            }
+        )
         response = self.client.get(link)
         self.assertEqual(response.status_code, 404)
 
@@ -38,7 +50,11 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_event(self):
         """events don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_event=True)
+        testutils.reply_thread(
+            self.thread,
+            poster=self.user,
+            is_event=True,
+        )
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -46,7 +62,10 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_thread(self):
         """user thread shows in feed"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
 
         # this post will not show in feed
         testutils.reply_thread(thread, poster=self.user)
@@ -58,7 +77,10 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
     def test_user_thread_anonymous(self):
         """user thread shows in feed requested by unauthenticated user"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
 
         self.logout_user()
 
@@ -72,17 +94,29 @@ class UserPostsApiTests(ThreadsApiTestCase):
     def setUp(self):
         super(UserPostsApiTests, self).setUp()
 
-        self.api_link = reverse('misago:api:user-posts', kwargs={'pk': self.user.pk})
+        self.api_link = reverse(
+            'misago:api:user-posts', kwargs={
+                'pk': self.user.pk,
+            }
+        )
 
     def test_invalid_user_id(self):
         """api validates user id"""
-        link = reverse('misago:api:user-posts', kwargs={'pk': 'abcd'})
+        link = reverse(
+            'misago:api:user-posts', kwargs={
+                'pk': 'abcd',
+            }
+        )
         response = self.client.get(link)
         self.assertEqual(response.status_code, 404)
 
     def test_nonexistant_user_id(self):
         """api validates that user for id exists"""
-        link = reverse('misago:api:user-posts', kwargs={'pk': self.user.pk + 1})
+        link = reverse(
+            'misago:api:user-posts', kwargs={
+                'pk': self.user.pk + 1,
+            }
+        )
         response = self.client.get(link)
         self.assertEqual(response.status_code, 404)
 
@@ -94,7 +128,11 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_event(self):
         """events don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_event=True)
+        testutils.reply_thread(
+            self.thread,
+            poster=self.user,
+            is_event=True,
+        )
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -102,7 +140,11 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_hidden_post(self):
         """hidden posts don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_hidden=True)
+        testutils.reply_thread(
+            self.thread,
+            poster=self.user,
+            is_hidden=True,
+        )
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -110,7 +152,11 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_unapproved_post(self):
         """unapproved posts don't show in feeds at all"""
-        testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
+        testutils.reply_thread(
+            self.thread,
+            poster=self.user,
+            is_unapproved=True,
+        )
 
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
@@ -129,7 +175,10 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_thread(self):
         """user thread shows in feed"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
         post = testutils.reply_thread(thread, poster=self.user)
 
         response = self.client.get(self.api_link)
@@ -153,7 +202,10 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
     def test_user_thread_anonymous(self):
         """user thread shows in feed requested by unauthenticated user"""
-        thread = testutils.post_thread(category=self.category, poster=self.user)
+        thread = testutils.post_thread(
+            category=self.category,
+            poster=self.user,
+        )
         post = testutils.reply_thread(thread, poster=self.user)
 
         self.logout_user()

+ 6 - 1
misago/users/tests/test_user_model.py

@@ -8,7 +8,12 @@ from misago.users.models import User
 class UserManagerTests(TestCase):
     def test_create_user(self):
         """create_user created new user account successfully"""
-        user = User.objects.create_user('Bob', 'bob@test.com', 'Pass.123', set_default_avatar=True)
+        user = User.objects.create_user(
+            'Bob',
+            'bob@test.com',
+            'Pass.123',
+            set_default_avatar=True,
+        )
 
         db_user = User.objects.get(id=user.pk)
 

+ 18 - 3
misago/users/tests/test_user_signature_api.py

@@ -56,7 +56,12 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         self.user.is_signature_locked = False
         self.user.save()
 
-        response = self.client.post(self.link, data={'signature': ''})
+        response = self.client.post(
+            self.link,
+            data={
+                'signature': '',
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertFalse(response.json()['signature'])
@@ -70,7 +75,12 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         self.user.is_signature_locked = False
         self.user.save()
 
-        response = self.client.post(self.link, data={'signature': 'abcd' * 1000})
+        response = self.client.post(
+            self.link,
+            data={
+                'signature': 'abcd' * 1000,
+            },
+        )
         self.assertContains(response, 'too long', status_code=400)
 
     def test_post_good_signature(self):
@@ -82,7 +92,12 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         self.user.is_signature_locked = False
         self.user.save()
 
-        response = self.client.post(self.link, data={'signature': 'Hello, **bros**!'})
+        response = self.client.post(
+            self.link,
+            data={
+                'signature': 'Hello, **bros**!',
+            },
+        )
         self.assertEqual(response.status_code, 200)
 
         self.assertEqual(

+ 34 - 11
misago/users/tests/test_user_username_api.py

@@ -54,7 +54,12 @@ class UserUsernameTests(AuthenticatedUserTestCase):
         response_json = response.json()
         self.assertEqual(response_json['changes_left'], 0)
 
-        response = self.client.post(self.link, data={'username': 'Pointless'})
+        response = self.client.post(
+            self.link,
+            data={
+                'username': 'Pointless',
+            },
+        )
 
         self.assertContains(response, 'change your username now', status_code=400)
         self.assertTrue(self.user.username != 'Pointless')
@@ -67,7 +72,12 @@ class UserUsernameTests(AuthenticatedUserTestCase):
 
     def test_change_username_invalid_name(self):
         """api returns error 400 if new username is wrong"""
-        response = self.client.post(self.link, data={'username': '####'})
+        response = self.client.post(
+            self.link,
+            data={
+                'username': '####',
+            },
+        )
 
         self.assertContains(response, 'can only contain latin', status_code=400)
 
@@ -79,7 +89,12 @@ class UserUsernameTests(AuthenticatedUserTestCase):
         old_username = self.user.username
         new_username = 'NewUsernamu'
 
-        response = self.client.post(self.link, data={'username': new_username})
+        response = self.client.post(
+            self.link,
+            data={
+                'username': new_username,
+            },
+        )
 
         self.assertEqual(response.status_code, 200)
         options = response.json()['options']
@@ -138,9 +153,11 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'username': '',
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
 
         self.assertContains(response, "Enter new username", status_code=400)
@@ -150,9 +167,11 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'username': '$$$',
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
 
         self.assertContains(
@@ -166,9 +185,11 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'username': 'a',
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
 
         self.assertEqual(response.status_code, 400)
@@ -181,9 +202,11 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
         })
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'username': 'BobBoberson',
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
 
         self.assertEqual(response.status_code, 200)

+ 88 - 27
misago/users/tests/test_useradmin_views.py

@@ -77,18 +77,26 @@ class UserAdminViewsTests(AdminTestCase):
         user_pks = []
         for i in range(10):
             test_user = UserModel.objects.create_user(
-                'Bob%s' % i, 'bob%s@test.com' % i, 'pass123', requires_activation=1
+                'Bob%s' % i,
+                'bob%s@test.com' % i,
+                'pass123',
+                requires_activation=1,
             )
             user_pks.append(test_user.pk)
 
         response = self.client.post(
             reverse('misago:admin:users:accounts:index'),
-            data={'action': 'activate',
-                  'selected_items': user_pks}
+            data={
+                'action': 'activate',
+                'selected_items': user_pks,
+            }
         )
         self.assertEqual(response.status_code, 302)
 
-        inactive_qs = UserModel.objects.filter(id__in=user_pks, requires_activation=1)
+        inactive_qs = UserModel.objects.filter(
+            id__in=user_pks,
+            requires_activation=1,
+        )
         self.assertEqual(inactive_qs.count(), 0)
         self.assertIn("has been activated", mail.outbox[0].subject)
 
@@ -97,14 +105,19 @@ class UserAdminViewsTests(AdminTestCase):
         user_pks = []
         for i in range(10):
             test_user = UserModel.objects.create_user(
-                'Bob%s' % i, 'bob%s@test.com' % i, 'pass123', requires_activation=1
+                'Bob%s' % i,
+                'bob%s@test.com' % i,
+                'pass123',
+                requires_activation=1,
             )
             user_pks.append(test_user.pk)
 
         response = self.client.post(
             reverse('misago:admin:users:accounts:index'),
-            data={'action': 'ban',
-                  'selected_items': user_pks}
+            data={
+                'action': 'ban',
+                'selected_items': user_pks,
+            }
         )
         self.assertEqual(response.status_code, 200)
 
@@ -114,8 +127,8 @@ class UserAdminViewsTests(AdminTestCase):
                 'action': 'ban',
                 'selected_items': user_pks,
                 'ban_type': ['usernames', 'emails', 'domains', 'ip', 'ip_first', 'ip_two'],
-                'finalize': ''
-            }
+                'finalize': '',
+            },
         )
         self.assertEqual(response.status_code, 302)
         self.assertEqual(Ban.objects.count(), 24)
@@ -125,14 +138,19 @@ class UserAdminViewsTests(AdminTestCase):
         user_pks = []
         for i in range(10):
             test_user = UserModel.objects.create_user(
-                'Bob%s' % i, 'bob%s@test.com' % i, 'pass123', requires_activation=1
+                'Bob%s' % i,
+                'bob%s@test.com' % i,
+                'pass123',
+                requires_activation=1,
             )
             user_pks.append(test_user.pk)
 
         response = self.client.post(
             reverse('misago:admin:users:accounts:index'),
-            data={'action': 'delete_accounts',
-                  'selected_items': user_pks}
+            data={
+                'action': 'delete_accounts',
+                'selected_items': user_pks,
+            }
         )
         self.assertEqual(response.status_code, 302)
         self.assertEqual(UserModel.objects.count(), 1)
@@ -142,14 +160,19 @@ class UserAdminViewsTests(AdminTestCase):
         user_pks = []
         for i in range(10):
             test_user = UserModel.objects.create_user(
-                'Bob%s' % i, 'bob%s@test.com' % i, 'pass123', requires_activation=1
+                'Bob%s' % i,
+                'bob%s@test.com' % i,
+                'pass123',
+                requires_activation=1,
             )
             user_pks.append(test_user.pk)
 
         response = self.client.post(
             reverse('misago:admin:users:accounts:index'),
-            data={'action': 'delete_accounts',
-                  'selected_items': user_pks}
+            data={
+                'action': 'delete_accounts',
+                'selected_items': user_pks,
+            }
         )
         self.assertEqual(response.status_code, 302)
         self.assertEqual(UserModel.objects.count(), 1)
@@ -170,7 +193,7 @@ class UserAdminViewsTests(AdminTestCase):
                 'roles': six.text_type(authenticated_role.pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
-                'staff_level': '0'
+                'staff_level': '0',
             }
         )
         self.assertEqual(response.status_code, 302)
@@ -181,7 +204,11 @@ class UserAdminViewsTests(AdminTestCase):
     def test_edit_view(self):
         """edit user view changes account"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertEqual(response.status_code, 200)
@@ -222,7 +249,11 @@ class UserAdminViewsTests(AdminTestCase):
         This is regression test for issue #640
         """
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertEqual(response.status_code, 200)
@@ -255,7 +286,11 @@ class UserAdminViewsTests(AdminTestCase):
     def test_edit_make_admin(self):
         """edit user view allows super admin to make other user admin"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertContains(response, 'id="id_is_staff_1"')
@@ -290,7 +325,11 @@ class UserAdminViewsTests(AdminTestCase):
     def test_edit_make_superadmin_admin(self):
         """edit user view allows super admin to make other user super admin"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertContains(response, 'id="id_is_staff_1"')
@@ -328,7 +367,11 @@ class UserAdminViewsTests(AdminTestCase):
         self.user.save()
 
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertNotContains(response, 'id="id_is_staff_1"')
@@ -366,7 +409,11 @@ class UserAdminViewsTests(AdminTestCase):
         self.user.save()
 
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertContains(response, 'id="id_is_active_1"')
@@ -410,7 +457,11 @@ class UserAdminViewsTests(AdminTestCase):
         test_user.is_staff = True
         test_user.save()
 
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertContains(response, 'id="id_is_active_1"')
@@ -454,7 +505,11 @@ class UserAdminViewsTests(AdminTestCase):
         test_user.is_staff = True
         test_user.save()
 
-        test_link = reverse('misago:admin:users:accounts:edit', kwargs={'pk': test_user.pk})
+        test_link = reverse(
+            'misago:admin:users:accounts:edit', kwargs={
+                'pk': test_user.pk,
+            }
+        )
 
         response = self.client.get(test_link)
         self.assertNotContains(response, 'id="id_is_active_1"')
@@ -492,7 +547,9 @@ class UserAdminViewsTests(AdminTestCase):
         """delete user threads view deletes threads"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
         test_link = reverse(
-            'misago:admin:users:accounts:delete-threads', kwargs={'pk': test_user.pk}
+            'misago:admin:users:accounts:delete-threads', kwargs={
+                'pk': test_user.pk,
+            }
         )
 
         category = Category.objects.all_categories()[:1][0]
@@ -516,7 +573,9 @@ class UserAdminViewsTests(AdminTestCase):
         """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}
+            'misago:admin:users:accounts:delete-posts', kwargs={
+                'pk': test_user.pk,
+            }
         )
 
         category = Category.objects.all_categories()[:1][0]
@@ -541,7 +600,9 @@ class UserAdminViewsTests(AdminTestCase):
         """delete user account view deletes user account"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
         test_link = reverse(
-            'misago:admin:users:accounts:delete-account', kwargs={'pk': test_user.pk}
+            'misago:admin:users:accounts:delete-account', kwargs={
+                'pk': test_user.pk,
+            }
         )
 
         response = self.client.post(test_link, **self.AJAX_HEADER)

+ 47 - 17
misago/users/tests/test_users_api.py

@@ -90,7 +90,9 @@ class FollowersListTests(AuthenticatedUserTestCase):
     def test_filled_list(self):
         """user with followers returns 200"""
         test_follower = UserModel.objects.create_user(
-            "TestFollower", "test@follower.com", self.USER_PASSWORD
+            "TestFollower",
+            "test@follower.com",
+            self.USER_PASSWORD,
         )
         self.user.followed_by.add(test_follower)
 
@@ -101,7 +103,9 @@ class FollowersListTests(AuthenticatedUserTestCase):
     def test_filled_list_search(self):
         """followers list is searchable"""
         test_follower = UserModel.objects.create_user(
-            "TestFollower", "test@follower.com", self.USER_PASSWORD
+            "TestFollower",
+            "test@follower.com",
+            self.USER_PASSWORD,
         )
         self.user.followed_by.add(test_follower)
 
@@ -134,7 +138,9 @@ class FollowsListTests(AuthenticatedUserTestCase):
     def test_filled_list(self):
         """user with follows returns 200"""
         test_follower = UserModel.objects.create_user(
-            "TestFollower", "test@follower.com", self.USER_PASSWORD
+            "TestFollower",
+            "test@follower.com",
+            self.USER_PASSWORD,
         )
         self.user.follows.add(test_follower)
 
@@ -145,7 +151,9 @@ class FollowsListTests(AuthenticatedUserTestCase):
     def test_filled_list_search(self):
         """follows list is searchable"""
         test_follower = UserModel.objects.create_user(
-            "TestFollower", "test@follower.com", self.USER_PASSWORD
+            "TestFollower",
+            "test@follower.com",
+            self.USER_PASSWORD,
         )
         self.user.follows.add(test_follower)
 
@@ -172,7 +180,11 @@ class RankListTests(AuthenticatedUserTestCase):
 
     def test_empty_list(self):
         """tab rank without members returns 200"""
-        test_rank = Rank.objects.create(name="Test rank", slug="test-rank", is_tab=True)
+        test_rank = Rank.objects.create(
+            name="Test rank",
+            slug="test-rank",
+            is_tab=True,
+        )
 
         response = self.client.get(self.link % test_rank.pk)
         self.assertEqual(response.status_code, 200)
@@ -203,10 +215,18 @@ class RankListTests(AuthenticatedUserTestCase):
 
     def test_disabled_users(self):
         """api follows disabled users visibility"""
-        test_rank = Rank.objects.create(name="Test rank", slug="test-rank", is_tab=True)
+        test_rank = Rank.objects.create(
+            name="Test rank",
+            slug="test-rank",
+            is_tab=True,
+        )
 
         test_user = UserModel.objects.create_user(
-            'Visible', 'visible@te.com', 'Pass.123', rank=test_rank, is_active=False
+            'Visible',
+            'visible@te.com',
+            'Pass.123',
+            rank=test_rank,
+            is_active=False,
         )
 
         response = self.client.get(self.link % test_rank.pk)
@@ -245,7 +265,11 @@ class UserRetrieveTests(AuthenticatedUserTestCase):
         super(UserRetrieveTests, self).setUp()
 
         self.test_user = UserModel.objects.create_user('Tyrael', 't123@test.com', 'pass123')
-        self.link = reverse('misago:api:user-detail', kwargs={'pk': self.test_user.pk})
+        self.link = reverse(
+            'misago:api:user-detail', kwargs={
+                'pk': self.test_user.pk,
+            }
+        )
 
     def test_get_user(self):
         """api user retrieve endpoint has no showstoppers"""
@@ -305,7 +329,7 @@ class UserForumOptionsTests(AuthenticatedUserTestCase):
             data={
                 'limits_private_thread_invites_to': 541,
                 'subscribe_to_started_threads': 44,
-                'subscribe_to_replied_threads': 321
+                'subscribe_to_replied_threads': 321,
             }
         )
 
@@ -331,7 +355,7 @@ class UserForumOptionsTests(AuthenticatedUserTestCase):
             data={
                 'limits_private_thread_invites_to': 1,
                 'subscribe_to_started_threads': 2,
-                'subscribe_to_replied_threads': 1
+                'subscribe_to_replied_threads': 1,
             }
         )
         self.assertEqual(response.status_code, 200)
@@ -349,7 +373,7 @@ class UserForumOptionsTests(AuthenticatedUserTestCase):
                 'is_hiding_presence': True,
                 'limits_private_thread_invites_to': 1,
                 'subscribe_to_started_threads': 2,
-                'subscribe_to_replied_threads': 1
+                'subscribe_to_replied_threads': 1,
             }
         )
         self.assertEqual(response.status_code, 200)
@@ -367,7 +391,7 @@ class UserForumOptionsTests(AuthenticatedUserTestCase):
                 'is_hiding_presence': False,
                 'limits_private_thread_invites_to': 1,
                 'subscribe_to_started_threads': 2,
-                'subscribe_to_replied_threads': 1
+                'subscribe_to_replied_threads': 1,
             }
         )
         self.assertEqual(response.status_code, 200)
@@ -478,7 +502,9 @@ class UserBanTests(AuthenticatedUserTestCase):
         override_acl(self.user, {'can_see_ban_details': 1})
 
         Ban.objects.create(
-            check_type=Ban.USERNAME, banned_value=self.other_user.username, user_message='Nope!'
+            check_type=Ban.USERNAME,
+            banned_value=self.other_user.username,
+            user_message='Nope!',
         )
 
         response = self.client.get(self.link)
@@ -610,9 +636,11 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         )
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'with_content': True
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
         self.assertEqual(response.status_code, 200)
 
@@ -632,9 +660,11 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         )
 
         response = self.client.post(
-            self.link, json.dumps({
+            self.link,
+            json.dumps({
                 'with_content': False
-            }), content_type="application/json"
+            }),
+            content_type='application/json',
         )
         self.assertEqual(response.status_code, 200)
 

+ 8 - 2
misago/users/tests/test_validators.py

@@ -31,7 +31,10 @@ class ValidateEmailAvailableTests(TestCase):
 
 class ValidateEmailBannedTests(TestCase):
     def setUp(self):
-        Ban.objects.create(check_type=Ban.EMAIL, banned_value="ban@test.com")
+        Ban.objects.create(
+            check_type=Ban.EMAIL,
+            banned_value="ban@test.com",
+        )
 
     def test_unbanned_name(self):
         """unbanned email passes validation"""
@@ -76,7 +79,10 @@ class ValidateUsernameAvailableTests(TestCase):
 
 class ValidateUsernameBannedTests(TestCase):
     def setUp(self):
-        Ban.objects.create(check_type=Ban.USERNAME, banned_value="Bob")
+        Ban.objects.create(
+            check_type=Ban.USERNAME,
+            banned_value="Bob",
+        )
 
     def test_unbanned_name(self):
         """unbanned name passes validation"""

+ 9 - 15
misago/users/tokens.py

@@ -1,12 +1,3 @@
-import base64
-from hashlib import sha256
-from time import time
-
-from django.conf import settings
-from django.utils import six
-from django.utils.encoding import force_bytes
-
-
 """
 Token creation
 
@@ -16,6 +7,13 @@ Token is base encoded string containing three values:
 - hash unique for current state of user model
 - token checksum for discovering manipulations
 """
+import base64
+from hashlib import sha256
+from time import time
+
+from django.conf import settings
+from django.utils import six
+from django.utils.encoding import force_bytes
 
 
 def make(user, token_type):
@@ -63,9 +61,7 @@ def _make_checksum(obfuscated):
     return sha256(force_bytes('%s:%s' % (settings.SECRET_KEY, obfuscated))).hexdigest()[:8]
 
 
-"""
-Convenience functions for activation token
-"""
+# Convenience functions for activation token
 ACTIVATION_TOKEN = 'activation'
 
 
@@ -77,9 +73,7 @@ def is_activation_token_valid(user, token):
     return is_valid(user, ACTIVATION_TOKEN, token)
 
 
-"""
-Convenience functions for password change token
-"""
+# Convenience functions for password change token
 PASSWORD_CHANGE_TOKEN = 'change_password'
 
 

+ 4 - 16
misago/users/validators.py

@@ -19,11 +19,9 @@ from .bans import get_email_ban, get_username_ban
 USERNAME_RE = re.compile(r'^[0-9a-z]+$', re.IGNORECASE)
 
 UserModel = get_user_model()
-"""
-Email validators
-"""
 
 
+# E-mail validators
 def validate_email_available(value, exclude=None):
     try:
         user = UserModel.objects.get_by_email(value)
@@ -50,11 +48,7 @@ def validate_email(value, exclude=None):
     validate_email_banned(value)
 
 
-"""
-Username validators
-"""
-
-
+# Username validators
 def validate_username_available(value, exclude=None):
     try:
         user = UserModel.objects.get_by_username(value)
@@ -105,9 +99,7 @@ def validate_username(value, exclude=None):
     validate_username_banned(value)
 
 
-"""
-New account validators
-"""
+# New account validators
 SFS_API_URL = u'http://api.stopforumspam.org/api?email=%(email)s&ip=%(ip)s&f=json&confidence'  # noqa
 
 
@@ -144,11 +136,7 @@ def validate_gmail_email(request, form, cleaned_data):
         form.add_error('email', ValidationError(_("This email is not allowed.")))
 
 
-"""
-Registration validation
-"""
-
-
+# Registration validation
 def load_registration_validators(validators):
     loaded_validators = []
     for path in validators:

+ 2 - 2
misago/users/viewmodels/activeposters.py

@@ -17,14 +17,14 @@ class ActivePosters(object):
         return {
             'tracked_period': self.tracked_period,
             'results': ScoredUserSerializer(self.users, many=True).data,
-            'count': self.count
+            'count': self.count,
         }
 
     def get_template_context(self):
         return {
             'tracked_period': self.tracked_period,
             'users': self.users,
-            'users_count': self.count
+            'users_count': self.count,
         }
 
 

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

@@ -10,5 +10,5 @@ class UserPosts(UserThreads):
 
     def get_posts_queryset(self, user, profile, threads_queryset):
         return profile.post_set.select_related('thread', 'poster').filter(
-            thread_id__in=threads_queryset.values('id')
+            thread_id__in=threads_queryset.values('id'),
         )

+ 5 - 2
misago/users/viewmodels/rankusers.py

@@ -6,8 +6,11 @@ from misago.users.serializers import UserCardSerializer
 
 class RankUsers(object):
     def __init__(self, request, rank, page=0):
-        queryset = rank.user_set.select_related('rank', 'ban_cache',
-                                                'online_tracker').order_by('slug')
+        queryset = rank.user_set.select_related(
+            'rank',
+            'ban_cache',
+            'online_tracker',
+        ).order_by('slug')
 
         if not request.user.is_staff:
             queryset = queryset.filter(is_active=True)

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

@@ -16,7 +16,9 @@ class UserThreads(object):
         threads_queryset = self.get_threads_queryset(request, threads_categories, profile)
 
         posts_queryset = self.get_posts_queryset(request.user, profile, threads_queryset).filter(
-            is_event=False, is_hidden=False, is_unapproved=False
+            is_event=False,
+            is_hidden=False,
+            is_unapproved=False,
         ).order_by('-id')
 
         list_page = paginate(
@@ -51,7 +53,7 @@ class UserThreads(object):
 
     def get_posts_queryset(self, user, profile, threads_queryset):
         return profile.post_set.select_related('thread', 'poster').filter(
-            id__in=threads_queryset.values('first_post_id')
+            id__in=threads_queryset.values('first_post_id'),
         )
 
     def get_frontend_context(self):
@@ -64,7 +66,10 @@ class UserThreads(object):
         return context
 
     def get_template_context(self):
-        return {'posts': self.posts, 'paginator': self.paginator}
+        return {
+            'posts': self.posts,
+            'paginator': self.paginator,
+        }
 
 
 UserFeedSerializer = FeedSerializer.exclude_fields('poster')

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

@@ -23,7 +23,9 @@ def activation_view(f):
 
 @activation_view
 def request_activation(request):
-    request.frontend_context.update({'SEND_ACTIVATION_API': reverse('misago:api:send-activation')})
+    request.frontend_context.update({
+        'SEND_ACTIVATION_API': reverse('misago:api:send-activation'),
+    })
     return render(request, 'misago/activation/request.html')
 
 
@@ -73,7 +75,7 @@ def activate_by_token(request, pk, token):
     return render(
         request, 'misago/activation/done.html', {
             'message': message % {
-                'user': inactive_user.username
+                'user': inactive_user.username,
             },
         }
     )

+ 18 - 16
misago/users/views/admin/users.py

@@ -111,8 +111,7 @@ class UsersList(UserAdmin, generic.ListView):
 
             mail_users(request, inactive_users, mail_subject, 'misago/emails/activation/by_admin')
 
-            message = _("Selected users accounts have been activated.")
-            messages.success(request, message)
+            messages.success(request, _("Selected users accounts have been activated."))
 
     def action_ban(self, request, users):
         users = users.order_by('slug')
@@ -132,7 +131,7 @@ class UsersList(UserAdmin, generic.ListView):
                 ban_kwargs = {
                     'user_message': cleaned_data.get('user_message'),
                     'staff_message': cleaned_data.get('staff_message'),
-                    'expires_on': cleaned_data.get('expires_on')
+                    'expires_on': cleaned_data.get('expires_on'),
                 }
 
                 for user in users:
@@ -179,8 +178,7 @@ class UsersList(UserAdmin, generic.ListView):
                             banned_values.append(banned_value)
 
                 Ban.objects.invalidate_cache()
-                message = _("Selected users have been banned.")
-                messages.success(request, message)
+                messages.success(request, _("Selected users have been banned."))
                 return None
 
         return self.render(
@@ -195,9 +193,8 @@ class UsersList(UserAdmin, generic.ListView):
     def action_delete_accounts(self, request, users):
         for user in users:
             if user.is_staff or user.is_superuser:
-                message = _("%(user)s is admin and can't be deleted.")
-                mesage = message % {'user': user.username}
-                raise generic.MassActionError(mesage)
+                message = _("%(user)s is admin and can't be deleted.") % {'user': user.username}
+                raise generic.MassActionError(message)
 
         for user in users:
             user.delete()
@@ -208,15 +205,13 @@ class UsersList(UserAdmin, generic.ListView):
     def action_delete_all(self, request, users):
         for user in users:
             if user.is_staff or user.is_superuser:
-                message = _("%(user)s is admin and can't be deleted.")
-                mesage = message % {'user': user.username}
-                raise generic.MassActionError(mesage)
+                message = _("%(user)s is admin and can't be deleted.") % {'user': user.username}
+                raise generic.MassActionError(message)
 
         for user in users:
             user.delete(delete_content=True)
 
-        message = _("Selected users and their content has been deleted.")
-        messages.success(request, message)
+        messages.success(request, _("Selected users and their content has been deleted."))
 
         return self.render(
             request, template='misago/admin/users/delete.html', context={
@@ -318,7 +313,8 @@ class DeletionStep(UserAdmin, generic.ButtonView):
 
     def execute_step(self, user):
         raise NotImplementedError(
-            "execute_step method should return dict with number of deleted_count and is_completed keys"
+            "execute_step method should return dict with "
+            "number of deleted_count and is_completed keys"
         )
 
     def button_action(self, request, target):
@@ -345,7 +341,10 @@ class DeleteThreadsStep(DeletionStep):
         else:
             is_completed = True
 
-        return {'deleted_count': deleted_threads, 'is_completed': is_completed}
+        return {
+            'deleted_count': deleted_threads,
+            'is_completed': is_completed,
+        }
 
 
 class DeletePostsStep(DeletionStep):
@@ -375,7 +374,10 @@ class DeletePostsStep(DeletionStep):
         else:
             is_completed = True
 
-        return {'deleted_count': deleted_posts, 'is_completed': is_completed}
+        return {
+            'deleted_count': deleted_posts,
+            'is_completed': is_completed,
+        }
 
 
 class DeleteAccountStep(DeletionStep):

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

@@ -55,7 +55,7 @@ def confirm_email_change(request, token):
     return render(
         request, 'misago/options/credentials_changed.html', {
             'message': message % {
-                'user': request.user.username
+                'user': request.user.username,
             },
         }
     )
@@ -75,7 +75,7 @@ def confirm_password_change(request, token):
     return render(
         request, 'misago/options/credentials_changed.html', {
             'message': message % {
-                'user': request.user.username
+                'user': request.user.username,
             },
         }
     )