Просмотр исходного кода

#702: more work on fixdictsformatting

Rafał Pitoń 8 лет назад
Родитель
Сommit
c90379701a

+ 2 - 1
cleansource

@@ -2,4 +2,5 @@
 
 yapf -ir ${1:-misago} -e '*/project_template/**/*.py' -e '*/conf/defaults.py'
 isort -rc ${1:-misago}
-pylint ${1:-misago}
+pylint ${1:-misago}
+python pycodestyle.py ${1:-misago}

+ 5 - 0
extras/config.py

@@ -0,0 +1,5 @@
+from django.utils.six.moves import configparser
+
+
+yapf = configparser.ConfigParser()
+yapf.read('.style.yapf')

+ 45 - 9
extras/fixdictsformatting.py

@@ -9,6 +9,11 @@ from yapf.yapflib import pytree_utils
 
 from django.utils import six
 
+from .config import yapf as yapf_config
+
+
+MAX_LINE_LENGTH = yapf_config.getint('style', 'column_limit') + 1
+
 
 def fix_formatting(filesource):
     if not ('{'  in filesource and ('[' in filesource or '(' in filesource)):
@@ -34,7 +39,6 @@ def walk_dict_tree(node, children):
         if isinstance(prev, Leaf) and prev.value == ':':
             if isinstance(item, Leaf):
                 if six.text_type(item).startswith("\n"):
-                    # first case: intended string
                     item.replace(Leaf(
                         item.type,
                         item.value,
@@ -47,31 +51,52 @@ def walk_dict_tree(node, children):
 
 
 def walk_dedent_tree(node, children):
+    force_split_next = False
     for item in children:
         prev = item.prev_sibling
         if not prev:
             if isinstance(item, Leaf) and six.text_type(item).startswith("\n"):
-                # first case: intended string
+                prev = node.prev_sibling
+                next = node.next_sibling
+                final_length = 0
+
+                if prev and "\n" not in six.text_type(node).strip():
+                    final_length = prev.column + len(six.text_type(node).strip()) + 3
+
                 item.replace(Leaf(
                     item.type,
                     item.value,
                     prefix=' ',
                 ))
+
+                if final_length and final_length > MAX_LINE_LENGTH:
+                    # tell next call to walk_dedent_tree_node that we need
+                    # different stringformat tactic
+                    force_split_next = True
         elif isinstance(item, Node):
             for subitem in item.children[1:]:
-                walk_dedent_tree_node(subitem, subitem.children)
+                walk_dedent_tree_node(subitem, subitem.children, force_split_next)
+                force_split_next = False
 
 
-def walk_dedent_tree_node(node, children):
+def walk_dedent_tree_node(node, children, force_split_next=False):
     if six.text_type(node).startswith("\n"):
         if isinstance(node, Leaf):
             prev = node.prev_sibling
             is_followup = prev and prev.type == token.STRING and node.type == token.STRING
             if is_followup:
+                new_value = node.value
                 new_prefix = "\n%s" % (' ' * (len(prev.prefix.lstrip("\n")) / 4 * 4))
+
+                # insert linebreak after last string in braces, so its closing brace moves to new line
+                if not node.next_sibling:
+                    closing_bracket = node.parent.parent.children[-1]
+                    if not six.text_type(closing_bracket).startswith("\n"):
+                        new_value = "%s\n%s" % (node.value, (' ' * ((len(prev.prefix.lstrip("\n")) / 4 - 1) * 4)))
+
                 node.replace(Leaf(
                     node.type,
-                    "%s\n%s" % (node.value, (' ' * ((len(prev.prefix.lstrip("\n")) / 4 - 1) * 4))),
+                    new_value,
                     prefix=new_prefix,
                 ))
             else:
@@ -85,19 +110,30 @@ def walk_dedent_tree_node(node, children):
                 walk_dedent_tree_node(item, item.children)
     elif isinstance(node, Leaf):
         if node.type == token.STRING:
+            strings_tuple = node.parent.parent
+
+            container = strings_tuple.parent.children[0]
+            while isinstance(container, Node):
+                container = container.children[0]
+            indent = container.column + 4
+
             prev = node.prev_sibling
             next = node.next_sibling
 
-            is_opening = prev is None and six.text_type(node.parent.parent).strip()[0] == '('
+            is_opening = prev is None and six.text_type(strings_tuple).strip()[0] == '('
             has_followup = next and next.type == token.STRING
 
             if is_opening and has_followup:
-                new_prefix = "\n%s" % (' ' * (len(next.prefix.lstrip("\n")) / 4 * 4))
-
                 node.replace(Leaf(
                     node.type,
                     node.value,
-                    prefix=new_prefix,
+                    prefix="\n%s" % (' ' * indent),
+                ))
+            elif force_split_next:
+                node.replace(Leaf(
+                    node.type,
+                    "%s\n%s" % (node.value, (' ' * (indent - 4))),
+                    prefix="\n%s" % (' ' * indent),
                 ))
     else:
         for item in children:

+ 0 - 7
extras/utils.py

@@ -1,7 +0,0 @@
-import tokenize
-
-from django.utils import six
-
-
-def generate_tokens(filesource):
-    return list(tokenize.generate_tokens(six.StringIO(filesource).readline))

+ 47 - 55
misago/categories/forms.py

@@ -129,35 +129,32 @@ def CategoryFormFactory(instance):
 
     return type(
         'CategoryFormFinal', (CategoryFormBase, ), {
-            'new_parent':
-                AdminCategoryChoiceField(
-                    label=_("Parent category"),
-                    queryset=parent_queryset,
-                    initial=instance.parent,
-                    empty_label=None,
-                ),
-            'copy_permissions':
-                AdminCategoryChoiceField(
-                    label=_("Copy permissions"),
-                    help_text=_(
-                        "You can replace this category permissions with "
-                        "permissions copied from category selected here."
-                    ),
-                    queryset=Category.objects.all_categories(),
-                    empty_label=_("Don't copy permissions"),
-                    required=False,
+            'new_parent': AdminCategoryChoiceField(
+                label=_("Parent category"),
+                queryset=parent_queryset,
+                initial=instance.parent,
+                empty_label=None,
+            ),
+            'copy_permissions': AdminCategoryChoiceField(
+                label=_("Copy permissions"),
+                help_text=_(
+                    "You can replace this category permissions with "
+                    "permissions copied from category selected here."
                 ),
-            'archive_pruned_in':
-                AdminCategoryChoiceField(
-                    label=_("Archive"),
-                    help_text=_(
-                        "Instead of being deleted, pruned threads can be "
-                        "moved to designated category."
-                    ),
-                    queryset=Category.objects.all_categories(),
-                    empty_label=_("Don't archive pruned threads"),
-                    required=False,
+                queryset=Category.objects.all_categories(),
+                empty_label=_("Don't copy permissions"),
+                required=False,
+            ),
+            'archive_pruned_in': AdminCategoryChoiceField(
+                label=_("Archive"),
+                help_text=_(
+                    "Instead of being deleted, pruned threads can be "
+                    "moved to designated category."
                 ),
+                queryset=Category.objects.all_categories(),
+                empty_label=_("Don't archive pruned threads"),
+                required=False,
+            ),
         }
     )
 
@@ -190,14 +187,13 @@ class DeleteCategoryFormBase(forms.ModelForm):
 def DeleteFormFactory(instance):
     content_queryset = Category.objects.all_categories().order_by('lft')
     fields = {
-        'move_threads_to':
-            AdminCategoryChoiceField(
-                label=_("Move category threads to"),
-                queryset=content_queryset,
-                initial=instance.parent,
-                empty_label=_('Delete with category'),
-                required=False,
-            )
+        'move_threads_to': AdminCategoryChoiceField(
+            label=_("Move category threads to"),
+            queryset=content_queryset,
+            initial=instance.parent,
+            empty_label=_('Delete with category'),
+            required=False,
+        )
     }
 
     not_siblings = models.Q(lft__lt=instance.lft)
@@ -226,16 +222,14 @@ class CategoryRoleForm(forms.ModelForm):
 
 def RoleCategoryACLFormFactory(category, category_roles, selected_role):
     attrs = {
-        'category':
-            category,
-        'role':
-            forms.ModelChoiceField(
-                label=_("Role"),
-                required=False,
-                queryset=category_roles,
-                initial=selected_role,
-                empty_label=_("No access"),
-            )
+        'category': category,
+        'role': forms.ModelChoiceField(
+            label=_("Role"),
+            required=False,
+            queryset=category_roles,
+            initial=selected_role,
+            empty_label=_("No access"),
+        )
     }
 
     return type('RoleCategoryACLForm', (forms.Form, ), attrs)
@@ -243,16 +237,14 @@ def RoleCategoryACLFormFactory(category, category_roles, selected_role):
 
 def CategoryRolesACLFormFactory(role, category_roles, selected_role):
     attrs = {
-        'role':
-            role,
-        'category_role':
-            forms.ModelChoiceField(
-                label=_("Role"),
-                required=False,
-                queryset=category_roles,
-                initial=selected_role,
-                empty_label=_("No access"),
-            )
+        'role': role,
+        'category_role': forms.ModelChoiceField(
+            label=_("Role"),
+            required=False,
+            queryset=category_roles,
+            initial=selected_role,
+            empty_label=_("No access"),
+        )
     }
 
     return type('CategoryRolesACLForm', (forms.Form, ), attrs)

+ 6 - 12
misago/conf/tests/test_migrationutils.py

@@ -9,12 +9,9 @@ from misago.core import threadstore
 class DBConfMigrationUtilsTests(TestCase):
     def setUp(self):
         self.test_group = {
-            'key':
-                'test_group',
-            'name':
-                "Test settings",
-            'description':
-                "Those are test settings.",
+            'key': 'test_group',
+            'name': "Test settings",
+            'description': "Those are test settings.",
             'settings': [
                 {
                     'setting': 'fish_name',
@@ -61,12 +58,9 @@ class DBConfMigrationUtilsTests(TestCase):
         """migrate_settings_group changed group key"""
 
         new_group = {
-            'key':
-                'new_test_group',
-            'name':
-                "New test settings",
-            'description':
-                "Those are updated test settings.",
+            'key': 'new_test_group',
+            'name': "New test settings",
+            'description': "Those are updated test settings.",
             'settings': [
                 {
                     'setting': 'fish_new_name',

+ 6 - 12
misago/conf/tests/test_settings.py

@@ -41,12 +41,9 @@ class GatewaySettingsTests(TestCase):
     def test_setting_public(self):
         """get_public_settings returns public settings"""
         test_group = {
-            'key':
-                'test_group',
-            'name':
-                "Test settings",
-            'description':
-                "Those are test settings.",
+            'key': 'test_group',
+            'name': "Test settings",
+            'description': "Those are test settings.",
             'settings': [
                 {
                     'setting': 'fish_name',
@@ -83,12 +80,9 @@ class GatewaySettingsTests(TestCase):
     def test_setting_lazy(self):
         """lazy settings work"""
         test_group = {
-            'key':
-                'test_group',
-            'name':
-                "Test settings",
-            'description':
-                "Those are test settings.",
+            'key': 'test_group',
+            'name': "Test settings",
+            'description': "Those are test settings.",
             'settings': [
                 {
                     'setting': 'fish_name',

+ 23 - 36
misago/core/migrations/0002_basic_settings.py

@@ -12,15 +12,12 @@ _ = lambda x: x
 def create_basic_settings_group(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'basic',
-            'name':
-                _("Basic forum settings"),
-            'description':
-                _(
-                    "Those settings control most basic properties "
-                    "of your forum like its name or description."
-                ),
+            'key': 'basic',
+            'name': _("Basic forum settings"),
+            'description': _(
+                "Those settings control most basic properties "
+                "of your forum like its name or description."
+            ),
             'settings': [
                 {
                     'setting': 'forum_name',
@@ -34,26 +31,22 @@ def create_basic_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'forum_index_title',
-                    'name':
-                        _("Index title"),
-                    'description':
-                        _("You may set custon title on "
-                          "forum index by typing it here."),
-                    'legend':
-                        _("Forum index"),
+                    'setting': 'forum_index_title',
+                    'name': _("Index title"),
+                    'description': _(
+    "You may set custon title on "
+    "forum index by typing it here."
+),
+                    'legend': _("Forum index"),
                     'field_extra': {
                         'max_length': 255
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
                 {
                     'setting': 'forum_index_meta_description',
                     'name': _("Meta Description"),
-                    'description': _("Short description of your forum "
-                                     "for internet crawlers."),
+                    'description': _("Short description of your forum for internet crawlers."),
                     'field_extra': {
                         'max_length': 255
                     },
@@ -71,8 +64,7 @@ def create_basic_settings_group(apps, schema_editor):
                 {
                     'setting': 'forum_branding_text',
                     'name': _("Branding text"),
-                    'description': _("Optional text displayed besides "
-                                     "brand image in navbar."),
+                    'description': _("Optional text displayed besides brand image in navbar."),
                     'value': "Misago",
                     'field_extra': {
                         'max_length': 255
@@ -80,18 +72,13 @@ def create_basic_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'email_footer',
-                    'name':
-                        _("E-mails footer"),
-                    'description':
-                        _(
-                            "Optional short message included "
-                            "at the end of e-mails sent by "
-                            "forum"
-                        ),
-                    'legend':
-                        _("Forum e-mails"),
+                    'setting': 'email_footer',
+                    'name': _("E-mails footer"),
+                    'description': _(
+                        "Optional short message included at "
+                        "the end of e-mails sent by forum."
+                    ),
+                    'legend': _("Forum e-mails"),
                     'field_extra': {
                         'max_length': 255
                     },

+ 46 - 73
misago/datamover/settings.py

@@ -34,79 +34,52 @@ def convert_allow_custom_avatars(old_value):
 
 
 SETTING_CONVERTER = {
-    'board_name':
-        copy_value('forum_name'),
-    'board_index_title':
-        copy_value('forum_index_title'),
-    'board_index_meta':
-        copy_value('forum_index_meta_description'),
-    'board_header':
-        copy_value('forum_branding_text'),
-    'email_footnote_plain':
-        copy_value('email_footer'),
-    'tos_title':
-        copy_value('terms_of_service_title'),
-    'tos_url':
-        copy_value('terms_of_service_link'),
-    'tos_content':
-        copy_value('terms_of_service'),
-    'board_credits':
-        copy_value('forum_footnote'),
-    'thread_name_min':
-        copy_value('thread_title_length_min'),
-    'thread_name_max':
-        copy_value('thread_title_length_max'),
-    'post_length_min':
-        copy_value('post_length_min'),
-    'account_activation':
-        map_value(
-            'account_activation', {
-                'none': 'none',
-                'user': 'user',
-                'admin': 'admin',
-                'block': 'closed',
-            }
-        ),
-    'username_length_min':
-        copy_value('username_length_min'),
-    'username_length_max':
-        copy_value('username_length_max'),
-    'password_length':
-        copy_value('password_length_min'),
-    'avatars_types':
-        convert_allow_custom_avatars,
-    'default_avatar':
-        copy_value('default_avatar'),
-    'upload_limit':
-        copy_value('avatar_upload_limit'),
-    'subscribe_start':
-        map_value('subscribe_start', {
-            '0': 'no',
-            '1': 'watch',
-            '2': 'watch_email',
-        }),
-    'subscribe_reply':
-        map_value('subscribe_reply', {
-            '0': 'no',
-            '1': 'watch',
-            '2': 'watch_email',
-        }),
-    'bots_registration':
-        map_value('captcha_type', {
-            'no': 'no',
-            'recaptcha': 're',
-            'qa': 'qa',
-        }),
-    'recaptcha_public':
-        copy_value('recaptcha_site_key'),
-    'recaptcha_private':
-        copy_value('recaptcha_secret_key'),
-    'qa_test':
-        copy_value('qa_question'),
-    'qa_test_help':
-        copy_value('qa_help_text'),
-    'qa_test_answers':
-        copy_value('qa_answers'),
+    'board_name': copy_value('forum_name'),
+    'board_index_title': copy_value('forum_index_title'),
+    'board_index_meta': copy_value('forum_index_meta_description'),
+    'board_header': copy_value('forum_branding_text'),
+    'email_footnote_plain': copy_value('email_footer'),
+    'tos_title': copy_value('terms_of_service_title'),
+    'tos_url': copy_value('terms_of_service_link'),
+    'tos_content': copy_value('terms_of_service'),
+    'board_credits': copy_value('forum_footnote'),
+    'thread_name_min': copy_value('thread_title_length_min'),
+    'thread_name_max': copy_value('thread_title_length_max'),
+    'post_length_min': copy_value('post_length_min'),
+    'account_activation': map_value(
+        'account_activation', {
+            'none': 'none',
+            'user': 'user',
+            'admin': 'admin',
+            'block': 'closed',
+        }
+    ),
+    'username_length_min': copy_value('username_length_min'),
+    'username_length_max': copy_value('username_length_max'),
+    'password_length': copy_value('password_length_min'),
+    'avatars_types': convert_allow_custom_avatars,
+    'default_avatar': copy_value('default_avatar'),
+    'upload_limit': copy_value('avatar_upload_limit'),
+    'subscribe_start': map_value('subscribe_start', {
+        '0': 'no',
+        '1': 'watch',
+        '2': 'watch_email',
+    }),
+    'subscribe_reply': map_value('subscribe_reply', {
+        '0': 'no',
+        '1': 'watch',
+        '2': 'watch_email',
+    }),
+    'bots_registration': map_value('captcha_type', {
+        'no': 'no',
+        'recaptcha': 're',
+        'qa': 'qa',
+    }),
+    'recaptcha_public': copy_value('recaptcha_site_key'),
+    'recaptcha_private': copy_value('recaptcha_secret_key'),
+    'qa_test': copy_value('qa_question'),
+    'qa_test_help': copy_value('qa_help_text'),
+    'qa_test_answers': copy_value('qa_answers'),
 }
 
 

+ 47 - 70
misago/legal/migrations/0001_initial.py

@@ -12,13 +12,12 @@ _ = lambda x: x
 def create_legal_settings_group(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'legal',
-            'name':
-                _("Legal information"),
-            'description':
-                _("Those settings allow you to set forum terms of "
-                  "service and privacy policy"),
+            'key': 'legal',
+            'name': _("Legal information"),
+            'description': _(
+    "Those settings allow you to set forum terms of "
+    "service and privacy policy"
+),
             'settings': [
                 {
                     'setting': 'terms_of_service_title',
@@ -33,49 +32,37 @@ def create_legal_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'terms_of_service_link',
-                    'name':
-                        _("Terms link"),
-                    'description':
-                        _(
-                            "If terms of service are located "
-                            "on other page, enter there its link."
-                        ),
-                    'value':
-                        "",
+                    'setting': 'terms_of_service_link',
+                    'name': _("Terms link"),
+                    'description': _(
+                        "If terms of service are located "
+                        "on other page, enter there its link."
+                    ),
+                    'value': "",
                     'field_extra': {
                         'max_length': 255,
                         'required': False,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
                 {
-                    'setting':
-                        'terms_of_service',
-                    'name':
-                        _("Terms contents"),
-                    'description':
-                        _(
-                            "Your forums can have custom terms of "
-                            "service page. To create it, write or "
-                            "paste here its contents. Full Misago "
-                            "markup is available for formatting."
-                        ),
-                    'value':
-                        "",
-                    'form_field':
-                        'textarea',
+                    'setting': 'terms_of_service',
+                    'name': _("Terms contents"),
+                    'description': _(
+                        "Your forums can have custom terms of "
+                        "service page. To create it, write or "
+                        "paste here its contents. Full Misago "
+                        "markup is available for formatting."
+                    ),
+                    'value': "",
+                    'form_field': 'textarea',
                     'field_extra': {
                         'max_length': 128000,
                         'required': False,
                         'rows': 8,
                     },
-                    'is_public':
-                        True,
-                    'is_lazy':
-                        True,
+                    'is_public': True,
+                    'is_lazy': True,
                 },
                 {
                     'setting': 'privacy_policy_title',
@@ -90,47 +77,37 @@ def create_legal_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'privacy_policy_link',
-                    'name':
-                        _("Policy link"),
-                    'description':
-                        _("If privacy policy is located on "
-                          "other page, enter there its link."),
-                    'value':
-                        "",
+                    'setting': 'privacy_policy_link',
+                    'name': _("Policy link"),
+                    'description': _(
+    "If privacy policy is located on "
+    "other page, enter there its link."
+),
+                    'value': "",
                     'field_extra': {
                         'max_length': 255,
                         'required': False,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
                 {
-                    'setting':
-                        'privacy_policy',
-                    'name':
-                        _("Policy contents"),
-                    'description':
-                        _(
-                            "Your forums can have custom privacy "
-                            "policy page. To create it, write or "
-                            "paste here its contents. Full Misago "
-                            "markup is available for formatting."
-                        ),
-                    'value':
-                        "",
-                    'form_field':
-                        'textarea',
+                    'setting': 'privacy_policy',
+                    'name': _("Policy contents"),
+                    'description': _(
+                        "Your forums can have custom privacy "
+                        "policy page. To create it, write or "
+                        "paste here its contents. Full Misago "
+                        "markup is available for formatting."
+                    ),
+                    'value': "",
+                    'form_field': 'textarea',
                     'field_extra': {
                         'max_length': 128000,
                         'required': False,
                         'rows': 8,
                     },
-                    'is_public':
-                        True,
-                    'is_lazy':
-                        True,
+                    'is_public': True,
+                    'is_lazy': True,
                 },
                 {
                     'setting': 'forum_footnote',
@@ -138,7 +115,7 @@ def create_legal_settings_group(apps, schema_editor):
                     'description': _("Short message displayed in forum footer."),
                     'legend': _("Forum footer"),
                     'field_extra': {
-                        'max_length': 300
+                        'max_length': 300,
                     },
                     'is_public': True,
                 },

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

@@ -50,11 +50,10 @@ def thread_merge_endpoint(request, thread, viewmodel):
     except Http404:
         return Response(
             {
-                'detail':
-                    _(
-                        "The thread you have entered link to doesn't "
-                        "exist or you don't have permission to see it."
-                    )
+                'detail': _(
+                    "The thread you have entered link to doesn't "
+                    "exist or you don't have permission to see it."
+                )
             },
             status=400,
         )

+ 23 - 27
misago/threads/forms.py

@@ -58,33 +58,29 @@ class AttachmentTypeForm(forms.ModelForm):
             'limit_downloads_to': _("Limit downloads to"),
         }
         help_texts = {
-            'extensions':
-                _("List of comma separated file extensions associated with this attachment type."),
-            'mimetypes':
-                _(
-                    "Optional list of comma separated mime types associated with this attachment type."
-                ),
-            'size_limit':
-                _(
-                    "Maximum allowed uploaded file size for this type, in kb. "
-                    "May be overriden via user permission."
-                ),
-            'status':
-                _("Controls this attachment type availability on your site."),
-            'limit_uploads_to':
-                _(
-                    "If you wish to limit option to upload files of this type to users with specific "
-                    "roles, select them on this list. Otherwhise don't select any roles to allow all "
-                    "users with permission to upload attachments to be able to upload attachments of "
-                    "this type."
-                ),
-            'limit_downloads_to':
-                _(
-                    "If you wish to limit option to download files of this type to users with "
-                    "specific roles, select them on this list. Otherwhise don't select any roles to "
-                    "allow all users with permission to download attachments to be able to download "
-                    " attachments of this type."
-                ),
+            'extensions': _(
+                "List of comma separated file extensions associated with this attachment type."
+            ),
+            'mimetypes': _(
+                "Optional list of comma separated mime types associated with this attachment type."
+            ),
+            'size_limit': _(
+                "Maximum allowed uploaded file size for this type, in kb. "
+                "May be overriden via user permission."
+            ),
+            'status': _("Controls this attachment type availability on your site."),
+            'limit_uploads_to': _(
+                "If you wish to limit option to upload files of this type to users with specific "
+                "roles, select them on this list. Otherwhise don't select any roles to allow all "
+                "users with permission to upload attachments to be able to upload attachments of "
+                "this type."
+            ),
+            'limit_downloads_to': _(
+                "If you wish to limit option to download files of this type to users with "
+                "specific roles, select them on this list. Otherwhise don't select any roles to "
+                "allow all users with permission to download attachments to be able to download "
+                "attachments of this type."
+            ),
         }
         widgets = {
             'limit_uploads_to': forms.CheckboxSelectMultiple,

+ 12 - 21
misago/threads/migrations/0002_threads_settings.py

@@ -12,12 +12,9 @@ _ = lambda x: x
 def create_threads_settings_group(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'threads',
-            'name':
-                _("Threads"),
-            'description':
-                _("Those settings control threads and posts."),
+            'key': 'threads',
+            'name': _("Threads"),
+            'description': _("Those settings control threads and posts."),
             'settings': [
                 {
                     'setting': 'thread_title_length_min',
@@ -57,24 +54,18 @@ def create_threads_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'post_length_max',
-                    'name':
-                        _("Maximum length"),
-                    'description':
-                        _(
-                            "Maximum allowed user post length. Enter zero to disable. "
-                            "Longer posts are more costful to parse and index."
-                        ),
-                    'python_type':
-                        'int',
-                    'value':
-                        60000,
+                    'setting': 'post_length_max',
+                    'name': _("Maximum length"),
+                    'description': _(
+                        "Maximum allowed user post length. Enter zero to disable. "
+                        "Longer posts are more costful to parse and index."
+                    ),
+                    'python_type': 'int',
+                    'value': 60000,
                     'field_extra': {
                         'min_value': 0,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
             ],
         }

+ 3 - 6
misago/threads/migrations/0003_attachment_types.py

@@ -25,13 +25,10 @@ ATTACHMENTS = [
         'size_limit': 3 * 1024
     },
     {
-        'name':
-            'PDF',
+        'name': 'PDF',
         'extensions': ('pdf', ),
-        'mimetypes':
-            ('application/pdf', 'application/x-pdf', 'application/x-bzpdf', 'application/x-gzpdf'),
-        'size_limit':
-            4 * 1024
+        'mimetypes': ('application/pdf', 'application/x-pdf', 'application/x-bzpdf', 'application/x-gzpdf'),
+        'size_limit': 4 * 1024
     },
     {
         'name': 'Text',

+ 12 - 21
misago/threads/migrations/0004_update_settings.py

@@ -12,12 +12,9 @@ _ = lambda x: x
 def update_threads_settings(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'threads',
-            'name':
-                _("Threads"),
-            'description':
-                _("Those settings control threads and posts."),
+            'key': 'threads',
+            'name': _("Threads"),
+            'description': _("Those settings control threads and posts."),
             'settings': [
                 {
                     'setting': 'thread_title_length_min',
@@ -57,24 +54,18 @@ def update_threads_settings(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'post_length_max',
-                    'name':
-                        _("Maximum length"),
-                    'description':
-                        _(
-                            "Maximum allowed user post length. Enter zero to disable. "
-                            "Longer posts are more costful to parse and index."
-                        ),
-                    'python_type':
-                        'int',
-                    'default_value':
-                        60000,
+                    'setting': 'post_length_max',
+                    'name': _("Maximum length"),
+                    'description': _(
+                        "Maximum allowed user post length. Enter zero to disable. "
+                        "Longer posts are more costful to parse and index."
+                    ),
+                    'python_type': 'int',
+                    'default_value': 60000,
                     'field_extra': {
                         'min_value': 0,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
             ],
         }

+ 2 - 2
misago/threads/search.py

@@ -47,8 +47,8 @@ class SearchThreads(SearchProvider):
 
         results = {
             'results': FeedSerializer(posts, many=True, context={
-                'user': self.request.user,
-            }).data
+            'user': self.request.user,
+        }).data
         }
         results.update(paginator)
 

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

@@ -341,16 +341,11 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         response = self.put(
             self.api_link,
             data={
-                'length':
-                    40,
-                'question':
-                    "Select two best colors",
-                'allowed_choices':
-                    2,
-                'allow_revotes':
-                    True,
-                'is_public':
-                    True,
+                'length': 40,
+                'question': "Select two best colors",
+                'allowed_choices': 2,
+                'allow_revotes': True,
+                'is_public': True,
                 'choices': [
                     {
                         'hash': 'aaaaaaaaaaaa',
@@ -429,16 +424,11 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
         response = self.put(
             self.api_link,
             data={
-                'length':
-                    40,
-                'question':
-                    "Select two best colors",
-                'allowed_choices':
-                    2,
-                'allow_revotes':
-                    True,
-                'is_public':
-                    True,
+                'length': 40,
+                'question': "Select two best colors",
+                'allowed_choices': 2,
+                'allow_revotes': True,
+                'is_public': True,
                 'choices': [
                     {
                         'hash': 'aaaaaaaaaaaa',

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

@@ -610,18 +610,12 @@ class EditReplyEditorApiTests(EditorApiTestCase):
         self.assertEqual(response.status_code, 200)
         self.assertEqual(
             response.json(), {
-                'id':
-                    self.post.pk,
-                'api':
-                    self.post.get_api_url(),
-                'post':
-                    self.post.original,
-                'can_protect':
-                    False,
-                'is_protected':
-                    self.post.is_protected,
-                'poster':
-                    self.post.poster_name,
+                'id': self.post.pk,
+                'api': self.post.get_api_url(),
+                'post': self.post.original,
+                'can_protect': False,
+                'is_protected': self.post.is_protected,
+                'poster': self.post.poster_name,
                 'attachments': [
                     AttachmentSerializer(attachments[1], context={'user': self.user}).data,
                     AttachmentSerializer(attachments[0], context={'user': self.user}).data,

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

@@ -31,16 +31,13 @@ LISTS_NAMES = {
 }
 
 LIST_DENIED_MESSAGES = {
-    'my':
-        ugettext_lazy("You have to sign in to see list of threads that you have started."),
-    'new':
-        ugettext_lazy("You have to sign in to see list of threads you haven't read."),
-    'unread':
-        ugettext_lazy("You have to sign in to see list of threads with new replies."),
-    'subscribed':
-        ugettext_lazy("You have to sign in to see list of threads you are subscribing."),
-    'unapproved':
-        ugettext_lazy("You have to sign in to see list of threads with unapproved posts."),
+    'my': ugettext_lazy("You have to sign in to see list of threads that you have started."),
+    'new': ugettext_lazy("You have to sign in to see list of threads you haven't read."),
+    'unread': ugettext_lazy("You have to sign in to see list of threads with new replies."),
+    'subscribed': ugettext_lazy("You have to sign in to see list of threads you are subscribing."),
+    'unapproved': ugettext_lazy(
+        "You have to sign in to see list of threads with unapproved posts."
+    ),
 }
 
 

+ 3 - 4
misago/threads/views/thread.py

@@ -44,10 +44,9 @@ class ThreadBase(View):
 
     def get_template_context(self, request, thread, posts):
         context = {
-            'url_name':
-                ':'.join(request.resolver_match.namespaces + [
-                    request.resolver_match.url_name,
-                ])
+            'url_name': ':'.join(request.resolver_match.namespaces + [
+                request.resolver_match.url_name,
+            ])
         }
 
         context.update(thread.get_template_context())

+ 4 - 2
misago/users/api/userendpoints/changepassword.py

@@ -25,8 +25,10 @@ def change_password_endpoint(request, pk=None):
         )
 
         return Response({
-            'detail': _("Password change confirmation link "
-                        "was sent to your address.")
+            'detail': _(
+                          "Password change confirmation link "
+                        "was sent to your address."
+                    )
         })
     else:
         return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

+ 1 - 1
misago/users/djangoadmin.py

@@ -82,7 +82,7 @@ class UserAdmin(admin.ModelAdmin):
             {
                 'fields': (
                     'username', 'email', 'rank', 'last_login', 'joined_on', 'is_staff',
-                    'is_superuser', 'edit_from_misago_link',
+                'is_superuser', 'edit_from_misago_link',
                 )
             },
         ],

+ 34 - 40
misago/users/forms/admin.py

@@ -222,18 +222,16 @@ def UserFormFactory(FormType, instance):
 
 def StaffFlagUserFormFactory(FormType, instance):
     staff_fields = {
-        'is_staff':
-            YesNoSwitch(
-                label=EditUserForm.IS_STAFF_LABEL,
-                help_text=EditUserForm.IS_STAFF_HELP_TEXT,
-                initial=instance.is_staff
-            ),
-        'is_superuser':
-            YesNoSwitch(
-                label=EditUserForm.IS_SUPERUSER_LABEL,
-                help_text=EditUserForm.IS_SUPERUSER_HELP_TEXT,
-                initial=instance.is_superuser
-            ),
+        'is_staff': YesNoSwitch(
+            label=EditUserForm.IS_STAFF_LABEL,
+            help_text=EditUserForm.IS_STAFF_HELP_TEXT,
+            initial=instance.is_staff
+        ),
+        'is_superuser': YesNoSwitch(
+            label=EditUserForm.IS_SUPERUSER_LABEL,
+            help_text=EditUserForm.IS_SUPERUSER_HELP_TEXT,
+            initial=instance.is_superuser
+        ),
     }
 
     return type('StaffUserForm', (FormType, ), staff_fields)
@@ -241,20 +239,18 @@ def StaffFlagUserFormFactory(FormType, instance):
 
 def UserIsActiveFormFactory(FormType, instance):
     is_active_fields = {
-        'is_active':
-            YesNoSwitch(
-                label=EditUserForm.IS_ACTIVE_LABEL,
-                help_text=EditUserForm.IS_ACTIVE_HELP_TEXT,
-                initial=instance.is_active
-            ),
-        'is_active_staff_message':
-            forms.CharField(
-                label=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_LABEL,
-                help_text=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_HELP_TEXT,
-                initial=instance.is_active_staff_message,
-                widget=forms.Textarea(attrs={'rows': 3}),
-                required=False
-            ),
+        'is_active': YesNoSwitch(
+            label=EditUserForm.IS_ACTIVE_LABEL,
+            help_text=EditUserForm.IS_ACTIVE_HELP_TEXT,
+            initial=instance.is_active
+        ),
+        'is_active_staff_message': forms.CharField(
+            label=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_LABEL,
+            help_text=EditUserForm.IS_ACTIVE_STAFF_MESSAGE_HELP_TEXT,
+            initial=instance.is_active_staff_message,
+            widget=forms.Textarea(attrs={'rows': 3}),
+            required=False
+        ),
     }
 
     return type('UserIsActiveForm', (FormType, ), is_active_fields)
@@ -325,20 +321,18 @@ def SearchUsersForm(*args, **kwargs):
         threadstore.set('misago_admin_roles_choices', roles_choices)
 
     extra_fields = {
-        'rank':
-            forms.TypedChoiceField(
-                label=_("Has rank"),
-                coerce=int,
-                required=False,
-                choices=ranks_choices,
-            ),
-        'role':
-            forms.TypedChoiceField(
-                label=_("Has role"),
-                coerce=int,
-                required=False,
-                choices=roles_choices,
-            )
+        'rank': forms.TypedChoiceField(
+            label=_("Has rank"),
+            coerce=int,
+            required=False,
+            choices=ranks_choices,
+        ),
+        'role': forms.TypedChoiceField(
+            label=_("Has role"),
+            coerce=int,
+            required=False,
+            choices=roles_choices,
+        )
     }
 
     FinalForm = type('SearchUsersFormFinal', (SearchUsersFormBase, ), extra_fields)

+ 20 - 24
misago/users/forms/auth.py

@@ -13,19 +13,17 @@ UserModel = get_user_model()
 
 class MisagoAuthMixin(object):
     error_messages = {
-        'empty_data':
-            _("Fill out both fields."),
-        'invalid_login':
-            _("Login or password is incorrect."),
-        'inactive_user':
-            _("You have to activate your account before "
-              "you will be able to sign in."),
-        'inactive_admin':
-            _(
-                "Your account has to be activated by "
-                "Administrator before you will be able "
-                "to sign in."
-            ),
+        'empty_data': _("Fill out both fields."),
+        'invalid_login': _("Login or password is incorrect."),
+        'inactive_user': _(
+    "You have to activate your account before "
+    "you will be able to sign in."
+),
+        'inactive_admin': _(
+            "Your account has to be activated by "
+            "Administrator before you will be able "
+            "to sign in."
+        ),
     }
 
     def confirm_user_active(self, user):
@@ -141,17 +139,15 @@ class ResendActivationForm(GetUserForm):
 
 class ResetPasswordForm(GetUserForm):
     error_messages = {
-        'inactive_user':
-            _(
-                "You have to activate your account before "
-                "you will be able to request new password."
-            ),
-        'inactive_admin':
-            _(
-                "Administrator has to activate your account "
-                "before you will be able to request "
-                "new password."
-            ),
+        'inactive_user': _(
+            "You have to activate your account before "
+            "you will be able to request new password."
+        ),
+        'inactive_admin': _(
+            "Administrator has to activate your account "
+            "before you will be able to request "
+            "new password."
+        ),
     }
 
     def confirm_allowed(self, user):

+ 49 - 72
misago/users/migrations/0002_users_settings.py

@@ -12,14 +12,11 @@ _ = lambda x: x
 def create_users_settings_group(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'users',
-            'name':
-                _("Users"),
-            'description':
-                _(
-                    "Those settings control user accounts default behaviour and features availability."
-                ),
+            'key': 'users',
+            'name': _("Users"),
+            'description': _(
+                "Those settings control user accounts default behaviour and features availability."
+            ),
             'settings': [
                 {
                     'setting': 'account_activation',
@@ -76,25 +73,18 @@ def create_users_settings_group(apps, schema_editor):
                     'is_public': True,
                 },
                 {
-                    'setting':
-                        'allow_custom_avatars',
-                    'name':
-                        _("Allow custom avatars"),
-                    'legend':
-                        _("Avatars"),
-                    'description':
-                        _(
-                            "Turning this option off will forbid "
-                            "forum users from using avatars from "
-                            "outside forums. Good for forums "
-                            "adressed at young users."
-                        ),
-                    'python_type':
-                        'bool',
-                    'value':
-                        True,
-                    'form_field':
-                        'yesno',
+                    'setting': 'allow_custom_avatars',
+                    'name': _("Allow custom avatars"),
+                    'legend': _("Avatars"),
+                    'description': _(
+                        "Turning this option off will forbid "
+                        "forum users from using avatars from "
+                        "outside forums. Good for forums "
+                        "adressed at young users."
+                    ),
+                    'python_type': 'bool',
+                    'value': True,
+                    'form_field': 'yesno',
                 },
                 {
                     'setting': 'default_avatar',
@@ -110,20 +100,15 @@ def create_users_settings_group(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'default_gravatar_fallback',
-                    'name':
-                        _("Fallback for default gravatar"),
-                    'description':
-                        _(
-                            "Select which avatar to use when user "
-                            "has no gravatar associated with his "
-                            "e-mail address."
-                        ),
-                    'value':
-                        'dynamic',
-                    'form_field':
-                        'select',
+                    'setting': 'default_gravatar_fallback',
+                    'name': _("Fallback for default gravatar"),
+                    'description': _(
+                        "Select which avatar to use when user "
+                        "has no gravatar associated with his "
+                        "e-mail address."
+                    ),
+                    'value': 'dynamic',
+                    'form_field': 'select',
                     'field_extra': {
                         'choices': [
                             ('dynamic', _("Individual")),
@@ -132,22 +117,18 @@ def create_users_settings_group(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'avatar_upload_limit',
-                    'name':
-                        _("Maximum size of uploaded avatar"),
-                    'description':
-                        _("Enter maximum allowed file size "
-                          "(in KB) for avatar uploads"),
-                    'python_type':
-                        'int',
-                    'value':
-                        1536,
+                    'setting': 'avatar_upload_limit',
+                    'name': _("Maximum size of uploaded avatar"),
+                    'description': _(
+    "Enter maximum allowed file size "
+    "(in KB) for avatar uploads"
+),
+                    'python_type': 'int',
+                    'value': 1536,
                     'field_extra': {
                         'min_value': 0,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
                 {
                     'setting': 'signature_length_max',
@@ -207,13 +188,12 @@ def create_users_settings_group(apps, schema_editor):
 
     migrate_settings_group(
         apps, {
-            'key':
-                'captcha',
-            'name':
-                _("CAPTCHA"),
-            'description':
-                _("Those settings allow you to combat automatic "
-                  "registrations on your forum."),
+            'key': 'captcha',
+            'name': _("CAPTCHA"),
+            'description': _(
+    "Those settings allow you to combat automatic "
+    "registrations on your forum."
+),
             'settings': [
                 {
                     'setting': 'captcha_type',
@@ -270,17 +250,14 @@ def create_users_settings_group(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'qa_answers',
-                    'name':
-                        _("Valid answers"),
-                    'description':
-                        _("Enter each answer in new line. "
-                          "Answers are case-insensitive."),
-                    'value':
-                        '',
-                    'form_field':
-                        'textarea',
+                    'setting': 'qa_answers',
+                    'name': _("Valid answers"),
+                    'description': _(
+    "Enter each answer in new line. "
+    "Answers are case-insensitive."
+),
+                    'value': '',
+                    'form_field': 'textarea',
                     'field_extra': {
                         'rows': 4,
                         'required': False,

+ 35 - 54
misago/users/migrations/0006_update_settings.py

@@ -13,14 +13,11 @@ _ = lambda x: x
 def update_users_settings(apps, schema_editor):
     migrate_settings_group(
         apps, {
-            'key':
-                'users',
-            'name':
-                _("Users"),
-            'description':
-                _(
-                    "Those settings control user accounts default behaviour and features availability."
-                ),
+            'key': 'users',
+            'name': _("Users"),
+            'description': _(
+                "Those settings control user accounts default behaviour and features availability."
+            ),
             'settings': [
                 {
                     'setting': 'account_activation',
@@ -62,25 +59,18 @@ def update_users_settings(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'allow_custom_avatars',
-                    'name':
-                        _("Allow custom avatars"),
-                    'legend':
-                        _("Avatars"),
-                    'description':
-                        _(
-                            "Turning this option off will forbid "
-                            "forum users from using avatars from "
-                            "outside forums. Good for forums "
-                            "adressed at young users."
-                        ),
-                    'python_type':
-                        'bool',
-                    'value':
-                        True,
-                    'form_field':
-                        'yesno',
+                    'setting': 'allow_custom_avatars',
+                    'name': _("Allow custom avatars"),
+                    'legend': _("Avatars"),
+                    'description': _(
+                        "Turning this option off will forbid "
+                        "forum users from using avatars from "
+                        "outside forums. Good for forums "
+                        "adressed at young users."
+                    ),
+                    'python_type': 'bool',
+                    'value': True,
+                    'form_field': 'yesno',
                 },
                 {
                     'setting': 'default_avatar',
@@ -96,20 +86,15 @@ def update_users_settings(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'default_gravatar_fallback',
-                    'name':
-                        _("Fallback for default gravatar"),
-                    'description':
-                        _(
-                            "Select which avatar to use when user "
-                            "has no gravatar associated with his "
-                            "e-mail address."
-                        ),
-                    'value':
-                        'dynamic',
-                    'form_field':
-                        'select',
+                    'setting': 'default_gravatar_fallback',
+                    'name': _("Fallback for default gravatar"),
+                    'description': _(
+                        "Select which avatar to use when user "
+                        "has no gravatar associated with his "
+                        "e-mail address."
+                    ),
+                    'value': 'dynamic',
+                    'form_field': 'select',
                     'field_extra': {
                         'choices': [
                             ('dynamic', _("Individual")),
@@ -118,22 +103,18 @@ def update_users_settings(apps, schema_editor):
                     },
                 },
                 {
-                    'setting':
-                        'avatar_upload_limit',
-                    'name':
-                        _("Maximum size of uploaded avatar"),
-                    'description':
-                        _("Enter maximum allowed file size "
-                          "(in KB) for avatar uploads"),
-                    'python_type':
-                        'int',
-                    'default_value':
-                        1536,
+                    'setting': 'avatar_upload_limit',
+                    'name': _("Maximum size of uploaded avatar"),
+                    'description': _(
+    "Enter maximum allowed file size "
+    "(in KB) for avatar uploads"
+),
+                    'python_type': 'int',
+                    'default_value': 1536,
                     'field_extra': {
                         'min_value': 0,
                     },
-                    'is_public':
-                        True,
+                    'is_public': True,
                 },
                 {
                     'setting': 'signature_length_max',

+ 10 - 20
misago/users/serializers/user.py

@@ -107,26 +107,16 @@ class UserSerializer(serializers.ModelSerializer, MutableFields):
 
     def get_api_url(self, obj):
         return {
-            'root':
-                reverse('misago:api:user-detail', kwargs={'pk': obj.pk}),
-            'follow':
-                reverse('misago:api:user-follow', kwargs={'pk': obj.pk}),
-            'ban':
-                reverse('misago:api:user-ban', kwargs={'pk': obj.pk}),
-            'moderate_avatar':
-                reverse('misago:api:user-moderate-avatar', kwargs={'pk': obj.pk}),
-            'moderate_username':
-                reverse('misago:api:user-moderate-username', kwargs={'pk': obj.pk}),
-            'delete':
-                reverse('misago:api:user-delete', kwargs={'pk': obj.pk}),
-            'followers':
-                reverse('misago:api:user-followers', kwargs={'pk': obj.pk}),
-            'follows':
-                reverse('misago:api:user-follows', kwargs={'pk': obj.pk}),
-            'threads':
-                reverse('misago:api:user-threads', kwargs={'pk': obj.pk}),
-            'posts':
-                reverse('misago:api:user-posts', kwargs={'pk': obj.pk}),
+            'root': reverse('misago:api:user-detail', kwargs={'pk': obj.pk}),
+            'follow': reverse('misago:api:user-follow', kwargs={'pk': obj.pk}),
+            'ban': reverse('misago:api:user-ban', kwargs={'pk': obj.pk}),
+            'moderate_avatar': reverse('misago:api:user-moderate-avatar', kwargs={'pk': obj.pk}),
+            'moderate_username': reverse('misago:api:user-moderate-username', kwargs={'pk': obj.pk}),
+            'delete': reverse('misago:api:user-delete', kwargs={'pk': obj.pk}),
+            'followers': reverse('misago:api:user-followers', kwargs={'pk': obj.pk}),
+            'follows': reverse('misago:api:user-follows', kwargs={'pk': obj.pk}),
+            'threads': reverse('misago:api:user-threads', kwargs={'pk': obj.pk}),
+            'posts': reverse('misago:api:user-posts', kwargs={'pk': obj.pk}),
         }
 
 

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

@@ -75,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,
+        },
         }
     )

+ 9 - 14
misago/users/views/admin/users.py

@@ -78,20 +78,15 @@ class UsersList(UserAdmin, generic.ListView):
             'confirmation': _("Are you sure you want to delete selected users?"),
         },
         {
-            'action':
-                'delete_all',
-            'name':
-                _("Delete all"),
-            'icon':
-                'fa fa-eraser',
-            'confirmation':
-                _(
-                    "Are you sure you want to delete selected "
-                    "users? This will also delete all content "
-                    "associated with their accounts."
-                ),
-            'is_atomic':
-                False,
+            'action': 'delete_all',
+            'name': _("Delete all"),
+            'icon': 'fa fa-eraser',
+            'confirmation': _(
+                "Are you sure you want to delete selected "
+                "users? This will also delete all content "
+                "associated with their accounts."
+            ),
+            'is_atomic': False,
         },
     ]
 

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

@@ -55,8 +55,8 @@ 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,
+        },
         }
     )

+ 5 - 7
pycodestyle.py

@@ -2,6 +2,7 @@
 Code style cleanups done after yapf
 """
 import argparse
+import codecs
 import os
 
 from extras import fixdictsformatting
@@ -15,21 +16,18 @@ CLEANUPS = [
 def walk_directory(root, dirs, files):
     for filename in files:
         if filename.lower().endswith('.py'):
-            with open(os.path.join(root, filename), 'r') as f:
+            with codecs.open(os.path.join(root, filename), 'r', 'utf-8') as f:
                 filesource = f.read()
 
             org_source = filesource
-            if 'migrate_settings_group' not in filesource:
-                continue
 
             for cleanup in CLEANUPS:
                 filesource = cleanup.fix_formatting(filesource)
 
             if org_source != filesource:
-                print '====' * 8
-                print os.path.join(root, filename)
-                print ''
-                print filesource
+                print 'afterclean: %s' % os.path.join(root, filename)
+                with codecs.open(os.path.join(root, filename), 'w', 'utf-8') as f:
+                    f.write(filesource)
 
 
 if __name__ == '__main__':