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

Make entire codebase pass pylint checks

rafalp 6 лет назад
Родитель
Сommit
7c6e8bb088
59 измененных файлов с 137 добавлено и 129 удалено
  1. 3 0
      .pylintrc
  2. 1 1
      .travis.yml
  3. 1 1
      misago/categories/management/commands/prunecategories.py
  4. 20 22
      misago/categories/tests/test_permissions_admin_views.py
  5. 3 1
      misago/categories/utils.py
  6. 1 1
      misago/core/tests/test_jsi18n.py
  7. 1 1
      misago/faker/management/commands/createfakecategories.py
  8. 3 1
      misago/faker/management/commands/createfakethreads.py
  9. 1 1
      misago/legal/tests/test_admin_views.py
  10. 1 1
      misago/markup/bbcode/inline.py
  11. 3 16
      misago/readtracker/tests/test_categoriestracker.py
  12. 1 1
      misago/readtracker/tests/test_dates.py
  13. 1 1
      misago/search/tests/test_searchproviders.py
  14. 1 1
      misago/threads/api/postingendpoint/__init__.py
  15. 1 1
      misago/threads/api/postingendpoint/participants.py
  16. 3 1
      misago/threads/api/threadendpoints/list.py
  17. 3 1
      misago/threads/api/threadendpoints/merge.py
  18. 5 1
      misago/threads/api/threadendpoints/patch.py
  19. 3 1
      misago/threads/api/threadpoll.py
  20. 1 1
      misago/threads/api/threadposts.py
  21. 1 1
      misago/threads/api/threads.py
  22. 2 4
      misago/threads/filtersearch.py
  23. 1 0
      misago/threads/models/attachment.py
  24. 1 0
      misago/threads/models/post.py
  25. 1 0
      misago/threads/moderation/exceptions.py
  26. 6 2
      misago/threads/permissions/threads.py
  27. 1 1
      misago/threads/serializers/pollvote.py
  28. 2 2
      misago/threads/tests/test_thread_pollcreate_api.py
  29. 2 2
      misago/threads/tests/test_thread_polledit_api.py
  30. 4 2
      misago/threads/tests/test_thread_postbulkdelete_api.py
  31. 1 1
      misago/threads/tests/test_thread_postmove_api.py
  32. 0 3
      misago/threads/tests/test_threads_moderation.py
  33. 1 2
      misago/threads/tests/test_validators.py
  34. 13 15
      misago/threads/utils.py
  35. 1 1
      misago/threads/viewmodels/posts.py
  36. 1 1
      misago/threads/views/goto.py
  37. 4 2
      misago/threads/views/list.py
  38. 1 1
      misago/threads/views/thread.py
  39. 1 0
      misago/users/api/userendpoints/signature.py
  40. 2 3
      misago/users/avatars/uploaded.py
  41. 1 1
      misago/users/bans.py
  42. 1 1
      misago/users/datadownloads/__init__.py
  43. 2 2
      misago/users/datadownloads/dataarchive.py
  44. 2 2
      misago/users/djangoadmin.py
  45. 1 1
      misago/users/forms/register.py
  46. 4 2
      misago/users/management/commands/createsuperuser.py
  47. 3 3
      misago/users/management/commands/listusedprofilefields.py
  48. 3 3
      misago/users/models/ban.py
  49. 1 1
      misago/users/models/online.py
  50. 1 1
      misago/users/models/user.py
  51. 1 1
      misago/users/namechanges.py
  52. 5 5
      misago/users/profilefields/basefields.py
  53. 2 2
      misago/users/profilefields/serializers.py
  54. 1 0
      misago/users/social/pipeline.py
  55. 1 1
      misago/users/tests/test_audittrail.py
  56. 0 2
      misago/users/tests/test_user_changeemail_api.py
  57. 1 1
      misago/users/tests/test_user_getters.py
  58. 1 1
      misago/users/validators.py
  59. 3 1
      misago/users/views/admin/users.py

+ 3 - 0
.pylintrc

@@ -10,14 +10,17 @@ max-line-length=88
 disable=
     abstract-method,
     arguments-differ,
+    assignment-from-none,
     attribute-defined-outside-init,
     bad-continuation,
+    cyclic-import,
     duplicate-code,
     expression-not-assigned,
     fixme,
     inconsistent-return-statements,
     invalid-name,
     missing-docstring,
+    model-no-explicit-unicode,  # pylint-django
     no-member,
     no-self-use,
     protected-access,

+ 1 - 1
.travis.yml

@@ -24,4 +24,4 @@ jobs:
         - pip install black pylint pylint-django
       script:
         - black --check devproject misago
-        - pylint misago/acl misago/admin misago/cache misago/conf misago/core
+        - pylint misago

+ 1 - 1
misago/categories/management/commands/prunecategories.py

@@ -14,7 +14,7 @@ class Command(BaseCommand):
 
     help = "Prunes categories"
 
-    def handle(self, *args, **options):
+    def handle(self, *args, **options):  # pylint: disable=too-many-branches
         now = timezone.now()
         synchronize_categories = []
 

+ 20 - 22
misago/categories/tests/test_permissions_admin_views.py

@@ -128,14 +128,14 @@ class CategoryRoleAdminViewsTests(AdminTestCase):
         root = Category.objects.root_category()
         for descendant in root.get_descendants():
             descendant.delete()
-        """
-        Create categories tree for test cases:
-
-        Category A
-          + Category B
-        Category C
-          + Category D
-        """
+
+        # Create categories tree for test cases:
+        #
+        # Category A
+        #   + Category B
+        # Category C
+        #   + Category D
+
         root = Category.objects.root_category()
         self.client.post(
             reverse("misago:admin:categories:nodes:new"),
@@ -149,9 +149,8 @@ class CategoryRoleAdminViewsTests(AdminTestCase):
         test_category = Category.objects.get(slug="category-a")
 
         self.assertEqual(Category.objects.count(), 3)
-        """
-        Create test roles
-        """
+
+        # Create test roles
         self.client.post(
             reverse("misago:admin:permissions:users:new"),
             data=mock_role_form_data(Role(), {"name": "Test Role A"}),
@@ -175,9 +174,8 @@ class CategoryRoleAdminViewsTests(AdminTestCase):
 
         role_comments = CategoryRole.objects.get(name="Test Comments")
         role_full = CategoryRole.objects.get(name="Test Full")
-        """
-        Test view itself
-        """
+
+        # Test view itself
         # See if form page is rendered
         response = self.client.get(
             reverse(
@@ -246,14 +244,14 @@ class CategoryRoleAdminViewsTests(AdminTestCase):
             )
         )
         self.assertEqual(response.status_code, 302)
-        """
-        Create categories tree for test cases:
-
-        Category A
-          + Category B
-        Category C
-          + Category D
-        """
+
+        # Create categories tree for test cases:
+        #
+        # Category A
+        #   + Category B
+        # Category C
+        #   + Category D
+
         root = Category.objects.root_category()
         self.client.post(
             reverse("misago:admin:categories:nodes:new"),

+ 3 - 1
misago/categories/utils.py

@@ -3,7 +3,9 @@ from ..readtracker import categoriestracker
 from .models import Category
 
 
-def get_categories_tree(user, user_acl, parent=None, join_posters=False):
+def get_categories_tree(
+    user, user_acl, parent=None, join_posters=False
+):  # pylint: disable=too-many-branches
     if not user_acl["visible_categories"]:
         return []
 

+ 1 - 1
misago/core/tests/test_jsi18n.py

@@ -30,7 +30,7 @@ class JsI18nUrlTests(TestCase):
                     response = self.client.get(reverse("django-i18n"))
                     if response.status_code != 200:
                         failed_languages.append(language)
-            except:  # pylint: disable=bare-except
+            except Exception:  # pylint: disable=broad-except
                 failed_languages.append(language)
 
         if failed_languages:

+ 1 - 1
misago/faker/management/commands/createfakecategories.py

@@ -29,7 +29,7 @@ class Command(BaseCommand):
             default=0,
         )
 
-    def handle(self, *args, **options):
+    def handle(self, *args, **options):  # pylint: disable=too-many-locals
         items_to_create = options["categories"]
         min_level = options["minlevel"]
 

+ 3 - 1
misago/faker/management/commands/createfakethreads.py

@@ -33,7 +33,9 @@ class Command(BaseCommand):
             default=5,
         )
 
-    def handle(self, *args, **options):
+    def handle(
+        self, *args, **options
+    ):  # pylint: disable=too-many-branches, too-many-locals
         items_to_create = options["threads"]
 
         categories = list(Category.objects.all_categories())

+ 1 - 1
misago/legal/tests/test_admin_views.py

@@ -22,7 +22,7 @@ class AgreementAdminViewsTests(AdminTestCase):
 
     def test_mass_delete(self):
         """adminview deletes multiple agreements"""
-        for i in range(10):
+        for _ in range(10):
             response = self.client.post(
                 reverse("misago:admin:users:agreements:new"),
                 data={"type": Agreement.TYPE_TOS, "text": "test agreement!"},

+ 1 - 1
misago/markup/bbcode/inline.py

@@ -18,7 +18,7 @@ class SimpleBBCodePattern(SimpleTagPattern):
     Case insensitive simple BBCode
     """
 
-    def __init__(self, bbcode, tag=None):
+    def __init__(self, bbcode, tag=None):  # pylint: disable=super-init-not-called
         self.pattern = r"(\[%s\](.*?)\[/%s\])" % (bbcode, bbcode)
         self.compiled_re = re.compile(
             "^(.*?)%s(.*?)$" % self.pattern, re.DOTALL | re.UNICODE | re.IGNORECASE

+ 3 - 16
misago/readtracker/tests/test_categoriestracker.py

@@ -164,22 +164,9 @@ class CategoriesTrackerTests(TestCase):
         self.assertFalse(self.category.is_read)
         self.assertTrue(self.category.is_new)
 
-    def test_user_first_read_post_unapproved_own_post(self):
-        """tracked thread with read first post and unapproved own post"""
-        thread = test.post_thread(self.category, started_on=timezone.now())
-        poststracker.save_read(self.user, thread.first_post)
-
-        test.reply_thread(
-            thread, posted_on=timezone.now(), poster=self.user, is_unapproved=True
-        )
-
-        categoriestracker.make_read_aware(self.user, self.user_acl, self.category)
-        self.assertFalse(self.category.is_read)
-        self.assertTrue(self.category.is_new)
-
     def test_user_unapproved_thread_unread_post(self):
         """tracked unapproved thread"""
-        thread = test.post_thread(
+        test.post_thread(
             self.category, started_on=timezone.now(), is_unapproved=True
         )
 
@@ -189,7 +176,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_unapproved_own_thread_unread_post(self):
         """tracked unapproved but visible thread"""
-        thread = test.post_thread(
+        test.post_thread(
             self.category,
             poster=self.user,
             started_on=timezone.now(),
@@ -202,7 +189,7 @@ class CategoriesTrackerTests(TestCase):
 
     def test_user_hidden_thread_unread_post(self):
         """tracked hidden thread"""
-        thread = test.post_thread(
+        test.post_thread(
             self.category, started_on=timezone.now(), is_hidden=True
         )
 

+ 1 - 1
misago/readtracker/tests/test_dates.py

@@ -40,7 +40,7 @@ class ReadTrackerDatesTests(TestCase):
         self.assertTrue(returned_cutoff_date > valid_cutoff_date)
         self.assertEqual(returned_cutoff_date, user.joined_on)
 
-    def test_get_cutoff_date_user(self):
+    def test_get_cutoff_date_anonymous_user(self):
         """passing anonymous user to get_cutoff_date has no effect"""
         user = MockAnonymousUser()
 

+ 1 - 1
misago/search/tests/test_searchproviders.py

@@ -54,7 +54,7 @@ class SearchProvidersTests(TestCase):
 
     def test_get_allowed_providers(self):
         """
-        allowed providers getter returns only providers that didn't raise an exception 
+        allowed providers getter returns only providers that didn't raise an exception
         in allow_search
         """
         searchproviders = SearchProviders([])

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

@@ -9,7 +9,7 @@ from ....conf import settings
 
 
 class PostingInterrupt(Exception):
-    def __init__(self, message):
+    def __init__(self, message):  # pylint: disable=super-init-not-called
         if not message:
             raise ValueError("You have to provide PostingInterrupt message.")
         self.message = message

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

@@ -87,7 +87,7 @@ class ParticipantsSerializer(serializers.Serializer):
             users.append(user)
 
         if len(usernames) != len(users):
-            invalid_usernames = set(usernames) - set([u.slug for u in users])
+            invalid_usernames = set(usernames) - {u.slug for u in users}
             sorted_usernames = sorted(invalid_usernames)
 
             message = _("One or more users could not be found: %(usernames)s")

+ 3 - 1
misago/threads/api/threadendpoints/list.py

@@ -31,7 +31,9 @@ class ThreadsList:
         )
 
     def get_threads(self, request, category, list_type, page):
-        return self.threads(request, category, list_type, page)
+        return self.threads(  # pylint: disable=not-callable
+            request, category, list_type, page
+        )
 
     def get_response_json(self, request, category, threads):
         return threads.get_frontend_context()

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

@@ -14,7 +14,9 @@ from ...serializers import (
 )
 
 
-def thread_merge_endpoint(request, thread, viewmodel):
+def thread_merge_endpoint(
+    request, thread, viewmodel
+):  # pylint: disable=too-many-branches
     allow_merge_thread(request.user_acl, thread)
 
     serializer = MergeThreadSerializer(

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

@@ -322,6 +322,7 @@ thread_patch_dispatcher.add("participants", patch_add_participant)
 
 
 def patch_remove_participant(request, thread, value):
+    # pylint: disable=undefined-loop-variable
     try:
         user_id = int(value)
     except (ValueError, TypeError):
@@ -349,6 +350,7 @@ thread_patch_dispatcher.remove("participants", patch_remove_participant)
 
 
 def patch_replace_owner(request, thread, value):
+    # pylint: disable=undefined-loop-variable
     try:
         user_id = int(value)
     except (ValueError, TypeError):
@@ -406,7 +408,9 @@ def thread_patch_endpoint(request, thread):
     return response
 
 
-def bulk_patch_endpoint(request, viewmodel):
+def bulk_patch_endpoint(
+    request, viewmodel
+):  # pylint: disable=too-many-branches, too-many-locals
     serializer = BulkPatchSerializer(data=request.data)
     if not serializer.is_valid():
         return Response(serializer.errors, status=400)

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

@@ -31,7 +31,9 @@ class ViewSet(viewsets.ViewSet):
     thread = None
 
     def get_thread(self, request, thread_pk):
-        return self.thread(request, get_int_or_404(thread_pk)).unwrap()
+        return self.thread(  # pylint: disable=not-callable
+            request, get_int_or_404(thread_pk)
+        ).unwrap()
 
     def get_poll(self, thread, pk):
         try:

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

@@ -32,7 +32,7 @@ class ViewSet(viewsets.ViewSet):
     def get_thread(
         self, request, pk, path_aware=False, read_aware=False, subscription_aware=False
     ):
-        return self.thread(
+        return self.thread(  # pylint: disable=not-callable
             request,
             get_int_or_404(pk),
             path_aware=path_aware,

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

@@ -29,7 +29,7 @@ class ViewSet(viewsets.ViewSet):
     def get_thread(
         self, request, pk, path_aware=False, read_aware=False, subscription_aware=False
     ):
-        return self.thread(
+        return self.thread(  # pylint: disable=not-callable
             request,
             get_int_or_404(pk),
             path_aware=path_aware,

+ 2 - 4
misago/threads/filtersearch.py

@@ -8,8 +8,6 @@ SEARCH_FILTERS = list(map(import_string, filters_list))
 
 def filter_search(search, filters=None):
     filters = filters or SEARCH_FILTERS
-
-    for filter in filters:
-        search = filter(search) or search
-
+    for search_filter in filters:
+        search = search_filter(search) or search
     return search

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

@@ -15,6 +15,7 @@ from ...core.utils import slugify
 
 
 def upload_to(instance, filename):
+    # pylint: disable=undefined-loop-variable
     spread_path = md5(str(instance.secret[:16]).encode()).hexdigest()
     secret = Attachment.generate_new_secret()
 

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

@@ -149,6 +149,7 @@ class Post(models.Model):
 
     @property
     def attachments(self):
+        # pylint: disable=access-member-before-definition
         if hasattr(self, "_hydrated_attachments_cache"):
             return self._hydrated_attachments_cache
 

+ 1 - 0
misago/threads/moderation/exceptions.py

@@ -1,3 +1,4 @@
+# pylint: disable=super-init-not-called
 class ModerationError(Exception):
     def __init__(self, message):
         self.message = message

+ 6 - 2
misago/threads/permissions/threads.py

@@ -1302,7 +1302,9 @@ def has_time_to_edit_post(user_acl, target):
     return True
 
 
-def exclude_invisible_threads(user_acl, categories, queryset):
+def exclude_invisible_threads(
+    user_acl, categories, queryset
+):  # pylint: disable=too-many-branches
     show_all = []
     show_accepted_visible = []
     show_accepted = []
@@ -1409,7 +1411,9 @@ def exclude_invisible_posts(user_acl, categories, queryset):
     return exclude_invisible_posts_in_category(user_acl, categories, queryset)
 
 
-def exclude_invisible_posts_in_categories(user_acl, categories, queryset):
+def exclude_invisible_posts_in_categories(
+    user_acl, categories, queryset
+):  # pylint: disable=too-many-branches
     show_all = []
     show_approved = []
     show_approved_owned = []

+ 1 - 1
misago/threads/serializers/pollvote.py

@@ -31,7 +31,7 @@ class NewVoteSerializer(serializers.Serializer):
             raise serializers.ValidationError(
                 _("One or more of poll choices were invalid.")
             )
-        if not len(clean_choices):
+        if clean_choices:
             raise serializers.ValidationError(_("You have to make a choice."))
 
         return clean_choices

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

@@ -277,7 +277,7 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
         self.assertTrue(response_json["is_public"])
 
         self.assertEqual(len(response_json["choices"]), 3)
-        self.assertEqual(len(set([c["hash"] for c in response_json["choices"]])), 3)
+        self.assertEqual(len({c["hash"] for c in response_json["choices"]}), 3)
         self.assertEqual(
             [c["label"] for c in response_json["choices"]], ["Red", "Green", "Blue"]
         )
@@ -300,6 +300,6 @@ class ThreadPollCreateTests(ThreadPollApiTestCase):
         self.assertTrue(poll.is_public)
 
         self.assertEqual(len(poll.choices), 3)
-        self.assertEqual(len(set([c["hash"] for c in poll.choices])), 3)
+        self.assertEqual(len({c["hash"] for c in poll.choices}), 3)
 
         self.assertEqual(self.user.audittrail_set.count(), 1)

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

@@ -307,7 +307,7 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
 
         # choices were updated
         self.assertEqual(len(response_json["choices"]), 3)
-        self.assertEqual(len(set([c["hash"] for c in response_json["choices"]])), 3)
+        self.assertEqual(len({c["hash"] for c in response_json["choices"]}), 3)
         self.assertEqual(
             [c["label"] for c in response_json["choices"]], ["Red", "Green", "Blue"]
         )
@@ -491,7 +491,7 @@ class ThreadPollEditTests(ThreadPollApiTestCase):
 
         # choices were updated
         self.assertEqual(len(response_json["choices"]), 3)
-        self.assertEqual(len(set([c["hash"] for c in response_json["choices"]])), 3)
+        self.assertEqual(len({c["hash"] for c in response_json["choices"]}), 3)
         self.assertEqual(
             [c["label"] for c in response_json["choices"]], ["Red", "Green", "Blue"]
         )

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

@@ -259,9 +259,11 @@ class PostBulkDeleteApiTests(ThreadsApiTestCase):
         ids = [self.posts[0].id, self.posts[-1].id]
 
         response = self.delete(self.api_link, ids)
-        self.thread = Thread.objects.get(pk=self.thread.pk)
+        self.assertEqual(response.status_code, 200)
 
+        self.thread = Thread.objects.get(pk=self.thread.pk)
         self.assertNotEqual(self.thread.last_post_id, ids[-1])
+
         for post in ids:
             with self.assertRaises(Post.DoesNotExist):
                 self.thread.post_set.get(pk=post)
@@ -273,8 +275,8 @@ class PostBulkDeleteApiTests(ThreadsApiTestCase):
         self.assertEqual(response.status_code, 200)
 
         self.thread = Thread.objects.get(pk=self.thread.pk)
-
         self.assertNotEqual(self.thread.last_post_id, self.posts[-1].pk)
+
         for post in self.posts:
             with self.assertRaises(Post.DoesNotExist):
                 self.thread.post_set.get(pk=post.pk)

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

@@ -177,7 +177,7 @@ class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     @patch_category_acl({"can_move_posts": True})
     def test_empty_data(self):
         """api handles empty data"""
-        other_thread = test.post_thread(self.category)
+        test.post_thread(self.category)
 
         response = self.client.post(self.api_link)
         self.assertEqual(response.status_code, 400)

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

@@ -18,9 +18,6 @@ class ThreadsModerationTests(AuthenticatedUserTestCase):
         self.category = Category.objects.all_categories()[:1][0]
         self.thread = test.post_thread(self.category)
 
-    def tearDown(self):
-        super().tearDown()
-
     def reload_thread(self):
         self.thread = Thread.objects.get(pk=self.thread.pk)
 

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

@@ -28,7 +28,6 @@ class ValidatePostLengthTests(TestCase):
         """too long post is rejected"""
         settings = Mock(post_length_min=1, post_length_max=2)
         with self.assertRaises(ValidationError):
-            post = "a" * settings.post_length_max
             validate_post_length(settings, "abc")
 
 
@@ -37,7 +36,7 @@ class ValidateThreadTitleTests(TestCase):
         """validate_thread_title is ok with valid titles"""
         settings = Mock(thread_title_length_min=1, thread_title_length_max=50)
 
-        VALID_TITLES = ["Lorem ipsum dolor met", "123 456 789 112" "Ugabugagagagagaga"]
+        VALID_TITLES = ["Lorem ipsum dolor met", "123 456 789 112", "Ugabugagagagagaga"]
 
         for title in VALID_TITLES:
             validate_thread_title(settings, title)

+ 13 - 15
misago/threads/utils.py

@@ -1,6 +1,6 @@
 from urllib.parse import urlparse
 
-from django.urls import resolve
+from django.urls import Resolver404, resolve
 
 from .models import PostLike
 
@@ -38,27 +38,25 @@ SUPPORTED_THREAD_ROUTES = {
 }
 
 
-def get_thread_id_from_url(request, url):
-    try:
-        clean_url = str(url).strip()
-        bits = urlparse(clean_url)
-    except:
-        return None
+def get_thread_id_from_url(request, url):  # pylint: disable=too-many-return-statements
+    clean_url = str(url).strip()
+    url_bits = urlparse(clean_url)
 
-    if bits.netloc and bits.netloc != request.get_host():
+    if url_bits.netloc and url_bits.netloc != request.get_host():
         return None
 
-    if bits.path.startswith(request.get_host()):
-        clean_path = bits.path.lstrip(request.get_host())
+    if url_bits.path.startswith(request.get_host()):
+        clean_path = url_bits.path.lstrip(request.get_host())
     else:
-        clean_path = bits.path
+        clean_path = url_bits.path
+
+    wsgi_alias = request.path[: len(request.path_info) * -1]
+    if not wsgi_alias or not clean_path.startswith(wsgi_alias):
+        return None
 
     try:
-        wsgi_alias = request.path[: len(request.path_info) * -1]
-        if wsgi_alias and not clean_path.startswith(wsgi_alias):
-            return None
         resolution = resolve(clean_path[len(wsgi_alias) :])
-    except:
+    except Resolver404:
         return None
 
     if not resolution.namespaces:

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

@@ -12,7 +12,7 @@ __all__ = ["ThreadPosts"]
 
 
 class ViewModel:
-    def __init__(self, request, thread, page):
+    def __init__(self, request, thread, page):  # pylint: disable=too-many-locals
         try:
             thread_model = thread.unwrap()
         except AttributeError:

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

@@ -31,7 +31,7 @@ class GotoView(View):
         return self.get_redirect(thread, target_post, target_page)
 
     def get_thread(self, request, pk, slug):
-        return self.thread(request, pk, slug)
+        return self.thread(request, pk, slug)  # pylint: disable=not-callable
 
     def test_permissions(self, request, thread):
         pass

+ 4 - 2
misago/threads/views/list.py

@@ -32,10 +32,12 @@ class ThreadsList(View):
         return render(request, self.template_name, template_context)
 
     def get_category(self, request, **kwargs):
-        return self.category(request, **kwargs)
+        return self.category(request, **kwargs)  # pylint: disable=not-callable
 
     def get_threads(self, request, category, list_type, page):
-        return self.threads(request, category, list_type, page)
+        return self.threads(  # pylint: disable=not-callable
+            request, category, list_type, page
+        )
 
     def get_frontend_context(self, request, category, threads):
         context = self.get_default_frontend_context()

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

@@ -22,7 +22,7 @@ class ThreadBase(View):
         return render(request, self.template_name, template_context)
 
     def get_thread(self, request, pk, slug):
-        return self.thread(
+        return self.thread(  # pylint: disable=not-callable
             request,
             pk,
             slug,

+ 1 - 0
misago/users/api/userendpoints/signature.py

@@ -41,6 +41,7 @@ def get_signature_options(settings, user):
         options["signature"] = {"plain": user.signature, "html": user.signature_parsed}
 
         if not is_user_signature_valid(user):
+            # pylint: disable=unsupported-assignment-operation
             options["signature"]["html"] = None
 
     return Response(options)

+ 2 - 3
misago/users/avatars/uploaded.py

@@ -27,7 +27,7 @@ def validate_uploaded_file(settings, uploaded_file):
             temporary_file_path = Path(uploaded_file.temporary_file_path())
             if temporary_file_path.exists():
                 temporary_file_path.unlink()
-        except Exception:
+        except Exception:  # pylint: disable=broad-except
             pass
         raise e
 
@@ -43,8 +43,7 @@ def validate_extension(uploaded_file):
     for extension in ALLOWED_EXTENSIONS:
         if lowercased_name.endswith(extension):
             return True
-    else:
-        raise ValidationError(_("Uploaded file type is not allowed."))
+    raise ValidationError(_("Uploaded file type is not allowed."))
 
 
 def validate_mime(uploaded_file):

+ 1 - 1
misago/users/bans.py

@@ -47,7 +47,7 @@ def get_user_ban(user, cache_versions):
     try:
         ban_cache = user.ban_cache
         if not ban_cache.is_valid(cache_versions):
-            _set_user_ban_cache(user)
+            _set_user_ban_cache(user, cache_versions)
     except BanCache.DoesNotExist:
         user.ban_cache = BanCache(user=user)
         user.ban_cache = _set_user_ban_cache(user, cache_versions)

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

@@ -39,7 +39,7 @@ def prepare_user_data_download(download, logger=None):
             download.save()
             # todo: send an e-mail with download link
             return True
-        except Exception as e:
+        except Exception as e:  # pylint: disable=broad-except
             if logger:
                 logger.exception(e)
             return False

+ 2 - 2
misago/users/datadownloads/dataarchive.py

@@ -81,8 +81,8 @@ class DataArchive:
 
     def add_dict(self, name, value, date=None, directory=None):
         text_lines = []
-        for key, value in value.items():
-            text_lines.append("%s: %s" % (key, value))
+        for key, item in value.items():
+            text_lines.append("%s: %s" % (key, item))
         text = "\n".join(text_lines)
         return self.add_text(name, text, date=date, directory=directory)
 

+ 2 - 2
misago/users/djangoadmin.py

@@ -8,9 +8,9 @@ class UserAdminModel(ModelAdmin):
     """
     The model should be used for interaction of third party django apps with
     Misago's `User`.
-    
+
     Removes `new` and `delete` actions (use Misago admin for that).
-    
+
     Registration call is placed in :mod:`misago.users.admin`.
     The tests are in :mod:`misago.users.tests.test_djangoadmin_user`.
     """

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

@@ -69,7 +69,7 @@ class SocialAuthRegisterForm(BaseRegisterForm):
         self.clean_agreements(cleaned_data)
         self.raise_if_ip_banned()
 
-        validate_new_registration(self.request, cleaned_data, self)
+        validate_new_registration(self.request, cleaned_data, self.add_error)
 
         return cleaned_data
 

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

@@ -73,7 +73,9 @@ class Command(BaseCommand):
         self.stdin = options.get("stdin", sys.stdin)  # Used for testing
         return super().execute(*args, **options)
 
-    def handle(self, *args, **options):
+    def handle(
+        self, *args, **options
+    ):  # pylint: disable=too-many-branches, too-many-locals
         username = options.get("username")
         email = options.get("email")
         password = options.get("password")
@@ -121,7 +123,7 @@ class Command(BaseCommand):
                     try:
                         message = force_str("Enter displayed username: ")
                         raw_value = input(message).strip()
-                        validate_username(raw_value)
+                        validate_username(settings, raw_value)
                         username = raw_value
                     except ValidationError as e:
                         self.stderr.write("\n".join(e.messages))

+ 3 - 3
misago/users/management/commands/listusedprofilefields.py

@@ -13,13 +13,13 @@ class Command(BaseCommand):
         keys = {}
 
         for user in chunk_queryset(User.objects.all()):
-            for key in user.profile_fields.keys():
+            for key in user.profile_fields:
                 keys.setdefault(key, 0)
                 keys[key] += 1
 
         if keys:
-            max_len = max([len(k) for k in keys.keys()])
-            for key in sorted(keys.keys()):
+            max_len = max([len(k) for k in keys])
+            for key in sorted(keys):
                 space = " " * (max_len + 1 - len(key))
                 self.stdout.write("%s:%s%s" % (key, space, keys[key]))
         else:

+ 3 - 3
misago/users/models/ban.py

@@ -58,8 +58,8 @@ class BansManager(models.Manager):
                 return ban
             elif ban.check_type == self.model.IP and ip and ban.check_value(ip):
                 return ban
-        else:
-            raise Ban.DoesNotExist("specified values are not banned")
+
+        raise Ban.DoesNotExist("specified values are not banned")
 
 
 class Ban(models.Model):
@@ -108,7 +108,7 @@ class Ban(models.Model):
 
     def check_value(self, value):
         if "*" in self.banned_value:
-            regex = re.escape(self.banned_value).replace("\*", "(.*?)")
+            regex = re.escape(self.banned_value).replace(r"\*", r"(.*?)")
             return re.search("^%s$" % regex, value, re.IGNORECASE) is not None
         return self.banned_value.lower() == value.lower()
 

+ 1 - 1
misago/users/models/online.py

@@ -1,5 +1,5 @@
 from django.conf import settings
-from django.db import models
+from django.db import IntegrityError, models
 from django.utils import timezone
 
 

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

@@ -37,7 +37,7 @@ class UserManager(BaseUserManager):
         user.set_email(email)
         user.set_password(password)
 
-        if not "rank" in extra_fields:
+        if "rank" not in extra_fields:
             user.rank = Rank.objects.get_default()
 
         now = timezone.now()

+ 1 - 1
misago/users/namechanges.py

@@ -26,7 +26,7 @@ def get_left_namechanges(user, user_acl):
     valid_changes = get_valid_changes_queryset(user, user_acl)
     used_changes = valid_changes.count()
     if name_changes_allowed <= used_changes:
-        left = 0
+        return 0
     return name_changes_allowed - used_changes
 
 

+ 5 - 5
misago/users/profilefields/basefields.py

@@ -56,8 +56,8 @@ class ProfileField:
         return data
 
     def get_display_data(self, request, user):
-        value = user.profile_fields.get(self.fieldname, "")
-        if not self.readonly and not len(value):
+        value = user.profile_fields.get(self.fieldname, "").strip()
+        if not self.readonly and not value:
             return None
 
         data = self.get_value_display_data(request, user, value)
@@ -101,13 +101,13 @@ class ChoiceProfileField(ProfileField):
 
     def get_input_json(self, request, user):
         choices = []
-        for key, choice in self.get_choices():
+        for key, choice in self.get_choices():  # pylint: disable=not-an-iterable
             choices.append({"value": key, "label": choice})
 
         return {"type": "select", "choices": choices}
 
     def get_value_display_data(self, request, user, value):
-        for key, name in self.get_choices():
+        for key, name in self.get_choices():  # pylint: disable=not-an-iterable
             if key == value:
                 return {"text": str(name)}
 
@@ -115,7 +115,7 @@ class ChoiceProfileField(ProfileField):
         """custom search implementation for choice fields"""
         q_obj = Q(**{"profile_fields__%s__contains" % self.fieldname: criteria})
 
-        for key, choice in self.get_choices():
+        for key, choice in self.get_choices():  # pylint: disable=not-an-iterable
             if key and criteria.lower() in str(choice).lower():
                 q_obj = q_obj | Q(**{"profile_fields__%s" % self.fieldname: key})
 

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

@@ -13,8 +13,8 @@ def serialize_profilefields_data(request, profilefields, user):
             display_data = field.get_display_data(request, user)
             if display_data:
                 group_fields.append(display_data)
-        if can_edit and field.is_editable(request, user):
-            has_editable_fields = True
+            if can_edit and field.is_editable(request, user):
+                has_editable_fields = True
         if group_fields:
             data["groups"].append({"name": group["name"], "fields": group_fields})
 

+ 1 - 0
misago/users/social/pipeline.py

@@ -1,3 +1,4 @@
+# pylint: disable=keyword-arg-before-vararg
 import json
 
 from django.contrib.auth import get_user_model

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

@@ -167,7 +167,7 @@ class RemoveOldAuditTrailsTest(UserTestCase):
     def test_recent_audit_trail_is_kept(self):
         """remove_old_ips keeps recent audit trails"""
         user = self.get_authenticated_user()
-        audit_trail = create_user_audit_trail(user, USER_IP, self.obj)
+        create_user_audit_trail(user, USER_IP, self.obj)
 
         remove_old_ips.send(None)
 

+ 0 - 2
misago/users/tests/test_user_changeemail_api.py

@@ -101,8 +101,6 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
     def test_change_email_user_password_whitespace(self):
         """api supports users with whitespace around their passwords"""
         user_password = " old password "
-        new_password = " N3wP@55w0rd "
-
         new_email = "new@email.com"
 
         self.user.set_password(user_password)

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

@@ -25,7 +25,7 @@ def test_getting_user_by_username_supports_diacritics(db):
 
 
 def test_getting_user_by_username_is_not_doing_fuzzy_matching(db):
-    user = User.objects.create_user("User", "test@example.com")
+    User.objects.create_user("User", "test@example.com")
     with pytest.raises(User.DoesNotExist):
         User.objects.get_by_username("usar")
 

+ 1 - 1
misago/users/validators.py

@@ -145,7 +145,7 @@ REGISTRATION_VALIDATORS = list(map(import_string, validators_list))
 
 
 def raise_validation_error(*_):
-    raise ValidationError()
+    raise ValidationError("")  # Raised when message content can be discarded
 
 
 def validate_new_registration(request, cleaned_data, add_error=None, validators=None):

+ 3 - 1
misago/users/views/admin/users.py

@@ -130,7 +130,9 @@ class UsersList(UserAdmin, generic.ListView):
 
             messages.success(request, _("Selected users accounts have been activated."))
 
-    def action_ban(self, request, users):
+    def action_ban(
+        self, request, users
+    ):  # pylint: disable=too-many-locals, too-many-nested-blocks, too-many-branches
         users = users.order_by("slug")
         for user in users:
             if user.is_superuser: