Browse Source

fix #740: setup basic pylint, cleanup codebase a little, default yapf and cleansource scripts

Rafał Pitoń 8 years ago
parent
commit
be5bb6a97f
143 changed files with 240 additions and 426 deletions
  1. 2 2
      .gitignore
  2. 3 3
      .pylintrc
  3. 13 0
      .style.yapf
  4. 5 0
      cleansource
  5. 0 2
      misago/acl/api.py
  6. 1 1
      misago/acl/migrations/0002_acl_version_tracker.py
  7. 0 1
      misago/admin/admin.py
  8. 10 10
      misago/admin/urlpatterns.py
  9. 1 1
      misago/admin/urls.py
  10. 1 1
      misago/admin/views/generic/list.py
  11. 1 1
      misago/categories/apps.py
  12. 2 2
      misago/categories/migrations/0002_default_categories.py
  13. 4 4
      misago/categories/migrations/0003_categories_roles.py
  14. 3 3
      misago/categories/tests/test_categories_admin_views.py
  15. 3 9
      misago/categories/tests/test_category_model.py
  16. 8 8
      misago/categories/tests/test_prunecategories.py
  17. 2 2
      misago/categories/tests/test_synchronizecategories.py
  18. 0 3
      misago/categories/utils.py
  19. 1 1
      misago/categories/views/categoriesadmin.py
  20. 0 2
      misago/conf/context_processors.py
  21. 1 1
      misago/conf/models.py
  22. 0 2
      misago/core/exceptionhandler.py
  23. 1 1
      misago/core/migrations/0002_basic_settings.py
  24. 2 2
      misago/core/page.py
  25. 1 1
      misago/core/setup.py
  26. 1 1
      misago/core/shortcuts.py
  27. 0 2
      misago/core/templatetags/misago_pagetitle.py
  28. 1 2
      misago/core/tests/test_cachebuster.py
  29. 0 1
      misago/core/tests/test_decorators.py
  30. 1 1
      misago/core/tests/test_deprecations.py
  31. 0 1
      misago/core/tests/test_momentjs.py
  32. 0 2
      misago/core/tests/test_templatetags.py
  33. 1 1
      misago/core/tests/test_utils.py
  34. 0 8
      misago/core/utils.py
  35. 0 2
      misago/core/views.py
  36. 1 1
      misago/datamover/attachments.py
  37. 1 1
      misago/datamover/avatars.py
  38. 1 1
      misago/datamover/markup/quotes.py
  39. 0 1
      misago/datamover/threads.py
  40. 1 1
      misago/faker/management/commands/createfakebans.py
  41. 0 2
      misago/faker/management/commands/createfakecategories.py
  42. 3 5
      misago/faker/management/commands/createfakethreads.py
  43. 0 2
      misago/faker/management/commands/createfakeusers.py
  44. 1 1
      misago/legal/migrations/0001_initial.py
  45. 0 1
      misago/markup/api.py
  46. 1 1
      misago/markup/mentions.py
  47. 0 1
      misago/markup/parser.py
  48. 1 1
      misago/readtracker/apps.py
  49. 0 3
      misago/readtracker/models.py
  50. 2 1
      misago/readtracker/tests/test_readtracker.py
  51. 0 1
      misago/threads/api/postendpoints/edits.py
  52. 0 2
      misago/threads/api/postendpoints/split.py
  53. 1 1
      misago/threads/api/postingendpoint/attachments.py
  54. 0 2
      misago/threads/api/postingendpoint/reply.py
  55. 1 2
      misago/threads/api/postingendpoint/subscribe.py
  56. 1 1
      misago/threads/api/threadendpoints/merge.py
  57. 2 3
      misago/threads/api/threadendpoints/patch.py
  58. 1 1
      misago/threads/apps.py
  59. 1 1
      misago/threads/forms.py
  60. 0 1
      misago/threads/middleware.py
  61. 1 1
      misago/threads/migrations/0001_initial.py
  62. 1 2
      misago/threads/migrations/0002_threads_settings.py
  63. 1 2
      misago/threads/migrations/0003_attachment_types.py
  64. 3 3
      misago/threads/models/post.py
  65. 1 1
      misago/threads/models/thread.py
  66. 0 1
      misago/threads/moderation/threads.py
  67. 0 3
      misago/threads/paginator.py
  68. 0 1
      misago/threads/participants.py
  69. 1 3
      misago/threads/permissions/privatethreads.py
  70. 1 1
      misago/threads/permissions/threads.py
  71. 0 1
      misago/threads/serializers/attachment.py
  72. 0 2
      misago/threads/serializers/feed.py
  73. 0 1
      misago/threads/serializers/post.py
  74. 0 2
      misago/threads/templatetags/misago_poststags.py
  75. 7 7
      misago/threads/tests/test_attachments_api.py
  76. 3 3
      misago/threads/tests/test_clearattachments.py
  77. 1 4
      misago/threads/tests/test_events.py
  78. 1 2
      misago/threads/tests/test_floodprotection_middleware.py
  79. 12 12
      misago/threads/tests/test_gotoviews.py
  80. 17 2
      misago/threads/tests/test_post_model.py
  81. 3 3
      misago/threads/tests/test_privatethread_patch_api.py
  82. 0 1
      misago/threads/tests/test_privatethread_reply_api.py
  83. 1 3
      misago/threads/tests/test_privatethread_start_api.py
  84. 3 1
      misago/threads/tests/test_privatethreads_api.py
  85. 3 1
      misago/threads/tests/test_privatethreads_lists.py
  86. 1 13
      misago/threads/tests/test_subscriptions.py
  87. 2 2
      misago/threads/tests/test_synchronizethreads.py
  88. 1 6
      misago/threads/tests/test_thread_editreply_api.py
  89. 5 7
      misago/threads/tests/test_thread_merge_api.py
  90. 0 6
      misago/threads/tests/test_thread_postedits_api.py
  91. 6 9
      misago/threads/tests/test_thread_postmerge_api.py
  92. 1 1
      misago/threads/tests/test_thread_postpatch_api.py
  93. 0 1
      misago/threads/tests/test_thread_postread_api.py
  94. 0 5
      misago/threads/tests/test_thread_start_api.py
  95. 1 2
      misago/threads/tests/test_threadparticipant_model.py
  96. 1 1
      misago/threads/tests/test_threads_editor_api.py
  97. 8 7
      misago/threads/tests/test_threads_merge_api.py
  98. 1 1
      misago/threads/tests/test_threads_moderation.py
  99. 6 6
      misago/threads/tests/test_threadslists.py
  100. 5 6
      misago/threads/tests/test_threadview.py
  101. 1 1
      misago/threads/tests/test_validators.py
  102. 8 8
      misago/threads/urls/__init__.py
  103. 0 3
      misago/threads/views/admin/attachments.py
  104. 0 2
      misago/threads/views/admin/attachmenttypes.py
  105. 0 3
      misago/threads/views/attachment.py
  106. 5 6
      misago/threads/views/list.py
  107. 2 2
      misago/threads/views/thread.py
  108. 1 1
      misago/users/api/userendpoints/avatar.py
  109. 0 1
      misago/users/api/userendpoints/create.py
  110. 1 9
      misago/users/api/userendpoints/list.py
  111. 0 2
      misago/users/api/userendpoints/username.py
  112. 1 1
      misago/users/api/usernamechanges.py
  113. 2 4
      misago/users/api/users.py
  114. 1 4
      misago/users/apps.py
  115. 1 2
      misago/users/avatars/dynamic.py
  116. 0 3
      misago/users/avatars/uploaded.py
  117. 0 1
      misago/users/decorators.py
  118. 0 1
      misago/users/management/commands/buildactivepostersranking.py
  119. 0 3
      misago/users/middleware.py
  120. 1 1
      misago/users/migrations/0003_bans_version_tracker.py
  121. 1 1
      misago/users/migrations/0004_default_ranks.py
  122. 1 2
      misago/users/models/user.py
  123. 1 1
      misago/users/serializers/moderation.py
  124. 0 3
      misago/users/templatetags/misago_avatars.py
  125. 1 1
      misago/users/tests/test_auth_api.py
  126. 0 1
      misago/users/tests/test_auth_views.py
  127. 2 2
      misago/users/tests/test_avatars.py
  128. 1 1
      misago/users/tests/test_banadmin_views.py
  129. 0 2
      misago/users/tests/test_forgottenpassword_views.py
  130. 1 1
      misago/users/tests/test_invalidatebans.py
  131. 0 4
      misago/users/tests/test_user_avatar_api.py
  132. 0 1
      misago/users/tests/test_user_changepassword_api.py
  133. 2 2
      misago/users/tests/test_user_feeds_api.py
  134. 4 13
      misago/users/tests/test_user_signature_api.py
  135. 2 1
      misago/users/tests/test_user_username_api.py
  136. 2 44
      misago/users/tests/test_useradmin_views.py
  137. 0 2
      misago/users/tests/test_usernamechanges_api.py
  138. 0 15
      misago/users/tests/test_users_api.py
  139. 2 2
      misago/users/validators.py
  140. 0 1
      misago/users/viewmodels/rankusers.py
  141. 0 2
      misago/users/views/activation.py
  142. 9 6
      misago/users/views/admin/users.py
  143. 1 2
      misago/users/views/options.py

+ 2 - 2
.gitignore

@@ -39,8 +39,8 @@ coverage.xml
 report.txt
 report.txt
 flake8.txt
 flake8.txt
 
 
-# Translations
-*.mo
+# Pylint report
+pylint.txt
 
 
 # Mr Developer
 # Mr Developer
 .mr.developer.cfg
 .mr.developer.cfg

+ 3 - 3
.pylintrc

@@ -1,4 +1,4 @@
 [Basic]
 [Basic]
-disable=abstract-method,cyclic-import,duplicate-code,file-ignored,invalid-name,locally-disabled,missing-docstring,no-init,no-member,no-self-use,old-style-class,super-on-old-class,too-few-public-methods,too-many-ancestors,unused-argument
-max-line-length=120
-max-locals=20
+disable=all
+enable=function-redefined,import-self,redefined-outer-name,reimported,return-in-init,undefined-all-variable,undefined-variable,unreachable,unused-import,unused-variable
+reports=no

+ 13 - 0
.style.yapf

@@ -0,0 +1,13 @@
+[style]
+coalesce_brackets = true
+column_limit=100
+dedent_closing_brackets = true
+each_dict_entry_on_separate_line = true
+indent_dictionary_value = true
+join_multiple_lines = false
+spaces_before_comment = 4
+split_arguments_when_comma_terminated = true
+split_before_first_argument = true
+split_before_logical_operator = true
+split_before_named_assigns = true
+split_penalty_import_names = 2

+ 5 - 0
cleansource

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+isort -rc misago
+yapf -ir misago
+pylint misago

+ 0 - 2
misago/acl/api.py

@@ -10,8 +10,6 @@ properties defined by ACL providers within their "add_acl_to_target"
 """
 """
 import copy
 import copy
 
 
-from django.contrib.auth import get_user_model
-
 from misago.core import threadstore
 from misago.core import threadstore
 from misago.core.cache import cache
 from misago.core.cache import cache
 
 

+ 1 - 1
misago/acl/migrations/0002_acl_version_tracker.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 
 
 from misago.acl.constants import ACL_CACHEBUSTER
 from misago.acl.constants import ACL_CACHEBUSTER
 from misago.core.migrationutils import cachebuster_register_cache
 from misago.core.migrationutils import cachebuster_register_cache

+ 0 - 1
misago/admin/admin.py

@@ -1,4 +1,3 @@
-from django.conf.urls import url
 from django.utils.deprecation import MiddlewareMixin
 from django.utils.deprecation import MiddlewareMixin
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 

+ 10 - 10
misago/admin/urlpatterns.py

@@ -11,13 +11,13 @@ class URLPatterns(object):
             'path': path,
             'path': path,
             'parent': parent,
             'parent': parent,
             'namespace': namespace,
             'namespace': namespace,
-            })
+        })
 
 
-    def patterns(self, namespace, *urlpatterns):
+    def patterns(self, namespace, *new_patterns):
         self._patterns.append({
         self._patterns.append({
             'namespace': namespace,
             'namespace': namespace,
-            'urlpatterns': urlpatterns,
-            })
+            'urlpatterns': new_patterns,
+        })
 
 
     def get_child_patterns(self, parent):
     def get_child_patterns(self, parent):
         prefix = '%s:' % parent if parent else ''
         prefix = '%s:' % parent if parent else ''
@@ -26,8 +26,8 @@ class URLPatterns(object):
         for namespace in self._namespaces:
         for namespace in self._namespaces:
             if namespace['parent'] == parent:
             if namespace['parent'] == parent:
                 prefixed_namespace = prefix + namespace['namespace']
                 prefixed_namespace = prefix + namespace['namespace']
-                urlpatterns = self.get_child_patterns(prefixed_namespace)
-                included_patterns = include(urlpatterns, namespace=namespace['namespace'])
+                child_patterns = self.get_child_patterns(prefixed_namespace)
+                included_patterns = include(child_patterns, namespace=namespace['namespace'])
                 namespace_urlpatterns.append(url(namespace['path'], included_patterns))
                 namespace_urlpatterns.append(url(namespace['path'], included_patterns))
 
 
         return namespace_urlpatterns
         return namespace_urlpatterns
@@ -36,8 +36,8 @@ class URLPatterns(object):
         all_patterns = {}
         all_patterns = {}
         for urls in self._patterns:
         for urls in self._patterns:
             namespace = urls['namespace']
             namespace = urls['namespace']
-            urlpatterns = urls['urlpatterns']
-            all_patterns.setdefault(namespace, []).extend(urlpatterns)
+            added_patterns = urls['urlpatterns']
+            all_patterns.setdefault(namespace, []).extend(added_patterns)
 
 
         self.namespace_patterns = all_patterns
         self.namespace_patterns = all_patterns
 
 
@@ -45,8 +45,8 @@ class URLPatterns(object):
         root_urlpatterns = []
         root_urlpatterns = []
         for namespace in self._namespaces:
         for namespace in self._namespaces:
             if not namespace['parent']:
             if not namespace['parent']:
-                urlpatterns = self.get_child_patterns(namespace['namespace'])
-                included_patterns = include(urlpatterns, namespace=namespace['namespace'])
+                child_patterns = self.get_child_patterns(namespace['namespace'])
+                included_patterns = include(child_patterns, namespace=namespace['namespace'])
                 root_urlpatterns.append(url(namespace['path'], included_patterns))
                 root_urlpatterns.append(url(namespace['path'], included_patterns))
 
 
         return root_urlpatterns
         return root_urlpatterns

+ 1 - 1
misago/admin/urls.py

@@ -1,4 +1,4 @@
-from django.conf.urls import include, url
+from django.conf.urls import url
 
 
 from misago import admin
 from misago import admin
 
 

+ 1 - 1
misago/admin/views/generic/list.py

@@ -262,7 +262,7 @@ class ListView(AdminView):
         return self.clean_ordering(new_ordering)
         return self.clean_ordering(new_ordering)
 
 
     def clean_ordering(self, new_ordering):
     def clean_ordering(self, new_ordering):
-        for order_by, name in self.ordering:
+        for order_by, _ in self.ordering:
             if order_by == new_ordering:
             if order_by == new_ordering:
                 return order_by
                 return order_by
         else:
         else:

+ 1 - 1
misago/categories/apps.py

@@ -7,4 +7,4 @@ class MisagoCategoriesConfig(AppConfig):
     verbose_name = "Misago Categories"
     verbose_name = "Misago Categories"
 
 
     def ready(self):
     def ready(self):
-        from . import signals
+        from . import signals as _

+ 2 - 2
misago/categories/migrations/0002_default_categories.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
 from misago.core.utils import slugify
 from misago.core.utils import slugify
@@ -32,7 +32,7 @@ def create_default_categories_tree(apps, schema_editor):
 
 
     category_name = _("First category")
     category_name = _("First category")
 
 
-    category = Category.objects.create(
+    Category.objects.create(
         parent=root,
         parent=root,
         lft=4,
         lft=4,
         rght=5,
         rght=5,

+ 4 - 4
misago/categories/migrations/0003_categories_roles.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
 
 
@@ -11,7 +11,7 @@ def create_default_categories_roles(apps, schema_editor):
     """
     """
     CategoryRole = apps.get_model('misago_categories', 'CategoryRole')
     CategoryRole = apps.get_model('misago_categories', 'CategoryRole')
 
 
-    see_only = CategoryRole.objects.create(
+    CategoryRole.objects.create(
         name=_('See only'),
         name=_('See only'),
         permissions={
         permissions={
             # categories perms
             # categories perms
@@ -41,7 +41,7 @@ def create_default_categories_roles(apps, schema_editor):
         }
         }
     )
     )
 
 
-    reply_only = CategoryRole.objects.create(
+    CategoryRole.objects.create(
         name=_('Reply to threads'),
         name=_('Reply to threads'),
         permissions={
         permissions={
             # categories perms
             # categories perms
@@ -87,7 +87,7 @@ def create_default_categories_roles(apps, schema_editor):
         }
         }
     )
     )
 
 
-    standard_with_polls = CategoryRole.objects.create(
+    CategoryRole.objects.create(
         name=_('Start and reply threads, make polls'),
         name=_('Start and reply threads, make polls'),
         permissions={
         permissions={
             # categories perms
             # categories perms

+ 3 - 3
misago/categories/tests/test_categories_admin_views.py

@@ -392,7 +392,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCate):
 
 
     def test_delete_category_move_contents(self):
     def test_delete_category_move_contents(self):
         """category was deleted and its contents were moved"""
         """category was deleted and its contents were moved"""
-        for i in range(10):
+        for _ in range(10):
             testutils.post_thread(self.category_b)
             testutils.post_thread(self.category_b)
         self.assertEqual(Thread.objects.count(), 10)
         self.assertEqual(Thread.objects.count(), 10)
 
 
@@ -426,7 +426,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCate):
 
 
     def test_delete_category_and_contents(self):
     def test_delete_category_and_contents(self):
         """category and its contents were deleted"""
         """category and its contents were deleted"""
-        for i in range(10):
+        for _ in range(10):
             testutils.post_thread(self.category_b)
             testutils.post_thread(self.category_b)
 
 
         response = self.client.get(
         response = self.client.get(
@@ -458,7 +458,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCate):
 
 
     def test_delete_leaf_category(self):
     def test_delete_leaf_category(self):
         """category was deleted and its contents were moved"""
         """category was deleted and its contents were moved"""
-        for i in range(10):
+        for _ in range(10):
             testutils.post_thread(self.category_d)
             testutils.post_thread(self.category_d)
         self.assertEqual(Thread.objects.count(), 10)
         self.assertEqual(Thread.objects.count(), 10)
 
 

+ 3 - 9
misago/categories/tests/test_category_model.py

@@ -1,5 +1,3 @@
-from django.utils import timezone
-
 from misago.categories import THREADS_ROOT_NAME
 from misago.categories import THREADS_ROOT_NAME
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.core.testutils import MisagoTestCase
 from misago.core.testutils import MisagoTestCase
@@ -54,11 +52,7 @@ class CategoryModelTests(MisagoTestCase):
         self.category = Category.objects.all_categories()[:1][0]
         self.category = Category.objects.all_categories()[:1][0]
 
 
     def create_thread(self):
     def create_thread(self):
-        datetime = timezone.now()
-
-        thread = testutils.post_thread(self.category)
-
-        return thread
+        return testutils.post_thread(self.category)
 
 
     def assertCategoryIsEmpty(self):
     def assertCategoryIsEmpty(self):
         self.assertIsNone(self.category.last_post_on)
         self.assertIsNone(self.category.last_post_on)
@@ -114,7 +108,7 @@ class CategoryModelTests(MisagoTestCase):
 
 
     def test_delete_content(self):
     def test_delete_content(self):
         """delete_content empties category"""
         """delete_content empties category"""
-        for i in range(10):
+        for _ in range(10):
             self.create_thread()
             self.create_thread()
 
 
         self.category.synchronize()
         self.category.synchronize()
@@ -131,7 +125,7 @@ class CategoryModelTests(MisagoTestCase):
 
 
     def test_move_content(self):
     def test_move_content(self):
         """move_content moves category threads and posts to other category"""
         """move_content moves category threads and posts to other category"""
-        for i in range(10):
+        for _ in range(10):
             self.create_thread()
             self.create_thread()
         self.category.synchronize()
         self.category.synchronize()
 
 

+ 8 - 8
misago/categories/tests/test_prunecategories.py

@@ -22,12 +22,12 @@ class PruneCategoriesTests(TestCase):
         # post old threads with recent replies
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         started_on = timezone.now() - timedelta(days=30)
         posted_on = timezone.now()
         posted_on = timezone.now()
-        for t in range(10):
+        for _ in range(10):
             thread = testutils.post_thread(category, started_on=started_on)
             thread = testutils.post_thread(category, started_on=started_on)
             testutils.reply_thread(thread, posted_on=posted_on)
             testutils.reply_thread(thread, posted_on=posted_on)
 
 
         # post recent threads that will be preserved
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
 
 
         category.synchronize()
         category.synchronize()
         self.assertEqual(category.threads, 20)
         self.assertEqual(category.threads, 20)
@@ -58,12 +58,12 @@ class PruneCategoriesTests(TestCase):
 
 
         # post old threads with recent replies
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         started_on = timezone.now() - timedelta(days=30)
-        for t in range(10):
+        for _ in range(10):
             thread = testutils.post_thread(category, started_on=started_on)
             thread = testutils.post_thread(category, started_on=started_on)
             testutils.reply_thread(thread)
             testutils.reply_thread(thread)
 
 
         # post recent threads that will be preserved
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
 
 
         category.synchronize()
         category.synchronize()
         self.assertEqual(category.threads, 20)
         self.assertEqual(category.threads, 20)
@@ -104,12 +104,12 @@ class PruneCategoriesTests(TestCase):
         # post old threads with recent replies
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         started_on = timezone.now() - timedelta(days=30)
         posted_on = timezone.now()
         posted_on = timezone.now()
-        for t in range(10):
+        for _ in range(10):
             thread = testutils.post_thread(category, started_on=started_on)
             thread = testutils.post_thread(category, started_on=started_on)
             testutils.reply_thread(thread, posted_on=posted_on)
             testutils.reply_thread(thread, posted_on=posted_on)
 
 
         # post recent threads that will be preserved
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
 
 
         category.synchronize()
         category.synchronize()
         self.assertEqual(category.threads, 20)
         self.assertEqual(category.threads, 20)
@@ -153,12 +153,12 @@ class PruneCategoriesTests(TestCase):
 
 
         # post old threads with recent replies
         # post old threads with recent replies
         started_on = timezone.now() - timedelta(days=30)
         started_on = timezone.now() - timedelta(days=30)
-        for t in range(10):
+        for _ in range(10):
             thread = testutils.post_thread(category, started_on=started_on)
             thread = testutils.post_thread(category, started_on=started_on)
             testutils.reply_thread(thread)
             testutils.reply_thread(thread)
 
 
         # post recent threads that will be preserved
         # post recent threads that will be preserved
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
 
 
         category.synchronize()
         category.synchronize()
         self.assertEqual(category.threads, 20)
         self.assertEqual(category.threads, 20)

+ 2 - 2
misago/categories/tests/test_synchronizecategories.py

@@ -13,9 +13,9 @@ class SynchronizeCategoriesTests(TestCase):
         """command synchronizes categories"""
         """command synchronizes categories"""
         category = Category.objects.all_categories()[:1][0]
         category = Category.objects.all_categories()[:1][0]
 
 
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
         for thread in threads:
         for thread in threads:
-            [testutils.reply_thread(thread) for r in range(5)]
+            [testutils.reply_thread(thread) for _ in range(5)]
 
 
         category.threads = 0
         category.threads = 0
         category.posts = 0
         category.posts = 0

+ 0 - 3
misago/categories/utils.py

@@ -1,5 +1,4 @@
 from misago.acl import add_acl
 from misago.acl import add_acl
-from misago.core import threadstore
 from misago.readtracker import categoriestracker
 from misago.readtracker import categoriestracker
 
 
 from .models import Category
 from .models import Category
@@ -74,8 +73,6 @@ def get_category_path(category):
     if category.special_role:
     if category.special_role:
         return [category]
         return [category]
 
 
-    categories_dict = Category.objects.get_cached_categories_dict()
-
     category_path = []
     category_path = []
     while category and category.level > 0:
     while category and category.level > 0:
         category_path.append(category)
         category_path.append(category)

+ 1 - 1
misago/categories/views/categoriesadmin.py

@@ -39,7 +39,7 @@ class CategoriesList(CategoryAdmin, generic.ListView):
 
 
         children_lists = {}
         children_lists = {}
 
 
-        for i, item in enumerate(context['items']):
+        for item in context['items']:
             item.level_range = range(item.level - 1)
             item.level_range = range(item.level - 1)
             item.first = False
             item.first = False
             item.last = False
             item.last = False

+ 0 - 2
misago/conf/context_processors.py

@@ -1,5 +1,3 @@
-import json
-
 from django.contrib.staticfiles.templatetags.staticfiles import static
 from django.contrib.staticfiles.templatetags.staticfiles import static
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.translation import get_language
 from django.utils.translation import get_language

+ 1 - 1
misago/conf/models.py

@@ -1,7 +1,7 @@
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.db import models
 
 
-from . import hydrators, utils
+from . import utils
 
 
 
 
 class SettingsGroupsManager(models.Manager):
 class SettingsGroupsManager(models.Manager):

+ 0 - 2
misago/core/exceptionhandler.py

@@ -4,7 +4,6 @@ from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.urls import reverse
 from django.urls import reverse
 from django.utils import six
 from django.utils import six
-from django.utils.translation import gettext as _
 
 
 from . import errorpages
 from . import errorpages
 from .exceptions import AjaxError, Banned, ExplicitFirstPage, OutdatedSlug
 from .exceptions import AjaxError, Banned, ExplicitFirstPage, OutdatedSlug
@@ -46,7 +45,6 @@ def handle_outdated_slug_exception(request, exception):
     view_name = request.resolver_match.view_name
     view_name = request.resolver_match.view_name
 
 
     model = exception.args[0]
     model = exception.args[0]
-    model_name = model.__class__.__name__.lower()
     url_kwargs = request.resolver_match.kwargs
     url_kwargs = request.resolver_match.kwargs
     url_kwargs['slug'] = model.slug
     url_kwargs['slug'] = model.slug
 
 

+ 1 - 1
misago/core/migrations/0002_basic_settings.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 
 
 from misago.conf.migrationutils import migrate_settings_group
 from misago.conf.migrationutils import migrate_settings_group
 
 

+ 2 - 2
misago/core/page.py

@@ -42,7 +42,7 @@ class Page(object):
     def _insert_section(self, inserted_section, after=None, before=None):
     def _insert_section(self, inserted_section, after=None, before=None):
         if after:
         if after:
             new_sorted_list = []
             new_sorted_list = []
-            for index, section in enumerate(self._sorted_list):
+            for section in self._sorted_list:
                 new_sorted_list.append(section)
                 new_sorted_list.append(section)
                 if section['link'] == after:
                 if section['link'] == after:
                     new_sorted_list.append(inserted_section)
                     new_sorted_list.append(inserted_section)
@@ -52,7 +52,7 @@ class Page(object):
                 return False
                 return False
         elif before:
         elif before:
             new_sorted_list = []
             new_sorted_list = []
-            for index, section in enumerate(self._sorted_list):
+            for section in self._sorted_list:
                 if section['link'] == before:
                 if section['link'] == before:
                     new_sorted_list.append(inserted_section)
                     new_sorted_list.append(inserted_section)
                     new_sorted_list.append(section)
                     new_sorted_list.append(section)

+ 1 - 1
misago/core/setup.py

@@ -36,7 +36,7 @@ def get_misago_project_template():
 
 
 def start_misago_project():
 def start_misago_project():
     parser = OptionParser(usage="usage: %prog project_name")
     parser = OptionParser(usage="usage: %prog project_name")
-    (options, args) = parser.parse_args()
+    _, args = parser.parse_args()
 
 
     if len(args) != 1:
     if len(args) != 1:
         parser.error("project_name must be specified")
         parser.error("project_name must be specified")

+ 1 - 1
misago/core/shortcuts.py

@@ -23,7 +23,7 @@ def paginate(object_list, page, per_page, orphans=0,
         return paginator(
         return paginator(
             object_list, per_page, orphans=orphans,
             object_list, per_page, orphans=orphans,
             allow_empty_first_page=allow_empty_first_page).page(page)
             allow_empty_first_page=allow_empty_first_page).page(page)
-    except (EmptyPage, InvalidPage) as e:
+    except (EmptyPage, InvalidPage):
         raise Http404()
         raise Http404()
 
 
 
 

+ 0 - 2
misago/core/templatetags/misago_pagetitle.py

@@ -1,8 +1,6 @@
 from django import template
 from django import template
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 
 
-from misago.conf import settings
-
 
 
 register = template.Library()
 register = template.Library()
 
 

+ 1 - 2
misago/core/tests/test_cachebuster.py

@@ -1,5 +1,4 @@
-from misago.core import cachebuster, threadstore
-from misago.core.cache import cache
+from misago.core import cachebuster
 from misago.core.models import CacheVersion
 from misago.core.models import CacheVersion
 from misago.core.testutils import MisagoTestCase
 from misago.core.testutils import MisagoTestCase
 
 

+ 0 - 1
misago/core/tests/test_decorators.py

@@ -1,4 +1,3 @@
-from django.contrib.auth import get_user_model
 from django.test import TestCase, override_settings
 from django.test import TestCase, override_settings
 from django.urls import reverse
 from django.urls import reverse
 
 

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

@@ -1,6 +1,6 @@
 import warnings
 import warnings
 
 
-from django.test import TestCase, override_settings
+from django.test import TestCase
 from django.utils import six
 from django.utils import six
 
 
 from misago.core.deprecations import RemovedInMisagoWarning, warn
 from misago.core.deprecations import RemovedInMisagoWarning, warn

+ 0 - 1
misago/core/tests/test_momentjs.py

@@ -1,4 +1,3 @@
-from django.conf import settings
 from django.test import TestCase
 from django.test import TestCase
 
 
 from misago.core.momentjs import clean_language_name, get_locale_url
 from misago.core.momentjs import clean_language_name, get_locale_url

+ 0 - 2
misago/core/tests/test_templatetags.py

@@ -2,9 +2,7 @@ from django import forms
 from django.template import Context, Template, TemplateSyntaxError
 from django.template import Context, Template, TemplateSyntaxError
 from django.test import TestCase
 from django.test import TestCase
 
 
-from misago.core.shortcuts import paginate
 from misago.core.templatetags import misago_batch
 from misago.core.templatetags import misago_batch
-from misago.core.utils import encode_json_html
 
 
 
 
 class CaptureTests(TestCase):
 class CaptureTests(TestCase):

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

@@ -4,7 +4,7 @@ from __future__ import unicode_literals
 from django.test import TestCase
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.test.client import RequestFactory
 from django.urls import reverse
 from django.urls import reverse
-from django.utils import six, timezone
+from django.utils import six
 
 
 from misago.core.utils import (
 from misago.core.utils import (
     clean_return_path, format_plaintext_for_html, is_referer_local, is_request_to_misago,
     clean_return_path, format_plaintext_for_html, is_referer_local, is_request_to_misago,

+ 0 - 8
misago/core/utils.py

@@ -6,19 +6,11 @@ from django.http import Http404
 from django.urls import resolve, reverse
 from django.urls import resolve, reverse
 from django.utils import html, timezone
 from django.utils import html, timezone
 from django.utils.encoding import force_text
 from django.utils.encoding import force_text
-from django.utils.translation import ugettext_lazy as _
-from django.utils.translation import ungettext_lazy
 
 
 
 
 MISAGO_SLUGIFY = getattr(settings, 'MISAGO_SLUGIFY', 'misago.core.slugify.default')
 MISAGO_SLUGIFY = getattr(settings, 'MISAGO_SLUGIFY', 'misago.core.slugify.default')
 
 
 
 
-def slugify(string):
-    string = six.text_type(string)
-    string = unidecode(string)
-    return django_slugify(string.replace('_', ' ').strip())
-
-
 def resolve_slugify(path):
 def resolve_slugify(path):
     path_bits = path.split('.')
     path_bits = path.split('.')
     module, name = '.'.join(path_bits[:-1]), path_bits[-1]
     module, name = '.'.join(path_bits[:-1]), path_bits[-1]

+ 0 - 2
misago/core/views.py

@@ -4,8 +4,6 @@ from django.views import i18n
 from django.views.decorators.cache import cache_page
 from django.views.decorators.cache import cache_page
 from django.views.decorators.http import last_modified
 from django.views.decorators.http import last_modified
 
 
-from . import momentjs
-
 
 
 def forum_index(request):
 def forum_index(request):
     return # blow up as this view is normally non-reachable!
     return # blow up as this view is normally non-reachable!

+ 1 - 1
misago/datamover/attachments.py

@@ -5,7 +5,7 @@ import os
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.core.files import File
 from django.core.files import File
 
 
-from misago.threads.models import Attachment, AttachmentType, Post, Thread
+from misago.threads.models import Attachment, AttachmentType, Post
 from misago.threads.serializers import AttachmentSerializer
 from misago.threads.serializers import AttachmentSerializer
 
 
 from . import OLD_FORUM, fetch_assoc, localise_datetime, movedids
 from . import OLD_FORUM, fetch_assoc, localise_datetime, movedids

+ 1 - 1
misago/datamover/avatars.py

@@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 
 
 from misago.conf import settings
 from misago.conf import settings
-from misago.users.avatars import dynamic, gallery, gravatar, store, uploaded
+from misago.users.avatars import dynamic, gravatar, store, uploaded
 
 
 from . import OLD_FORUM, fetch_assoc, movedids
 from . import OLD_FORUM, fetch_assoc, movedids
 
 

+ 1 - 1
misago/datamover/markup/quotes.py

@@ -11,7 +11,7 @@ def convert_quotes_to_bbcode(post):
     quote_author = None
     quote_author = None
     quote = []
     quote = []
 
 
-    for i, line in enumerate(post.splitlines() + ['']):
+    for line in post.splitlines() + ['']:
         if in_quote:
         if in_quote:
             if line.startswith('>'):
             if line.startswith('>'):
                 quote.append(line[1:].lstrip())
                 quote.append(line[1:].lstrip())

+ 0 - 1
misago/datamover/threads.py

@@ -4,7 +4,6 @@ from django.contrib.auth import get_user_model
 from django.utils import timezone
 from django.utils import timezone
 
 
 from misago.categories.models import Category
 from misago.categories.models import Category
-from misago.threads.checksums import update_post_checksum
 from misago.threads.models import Post, PostEdit, PostLike, Thread, ThreadParticipant
 from misago.threads.models import Post, PostEdit, PostLike, Thread, ThreadParticipant
 
 
 from . import fetch_assoc, localise_datetime, markup, movedids
 from . import fetch_assoc, localise_datetime, markup, movedids

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

@@ -98,7 +98,7 @@ class Command(BaseCommand):
 
 
         created_count = 0
         created_count = 0
         show_progress(self, created_count, fake_bans_to_create)
         show_progress(self, created_count, fake_bans_to_create)
-        for i in range(fake_bans_to_create):
+        for _ in range(fake_bans_to_create):
             ban = Ban(check_type=random.randint(Ban.USERNAME, Ban.IP))
             ban = Ban(check_type=random.randint(Ban.USERNAME, Ban.IP))
             ban.banned_value = create_fake_test(fake, ban.check_type)
             ban.banned_value = create_fake_test(fake, ban.check_type)
 
 

+ 0 - 2
misago/faker/management/commands/createfakecategories.py

@@ -1,11 +1,9 @@
 import random
 import random
-import sys
 import time
 import time
 
 
 from faker import Factory
 from faker import Factory
 
 
 from django.core.management.base import BaseCommand
 from django.core.management.base import BaseCommand
-from django.utils.six.moves import range
 
 
 from misago.acl import version as acl_version
 from misago.acl import version as acl_version
 from misago.categories.models import Category, RoleCategoryACL
 from misago.categories.models import Category, RoleCategoryACL

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

@@ -47,8 +47,6 @@ class Command(BaseCommand):
 
 
         fake = Factory.create()
         fake = Factory.create()
 
 
-        total_users = UserModel.objects.count()
-
         self.stdout.write('Creating fake threads...\n')
         self.stdout.write('Creating fake threads...\n')
 
 
         message = '\nSuccessfully created %s fake threads in %s'
         message = '\nSuccessfully created %s fake threads in %s'
@@ -115,7 +113,7 @@ class Command(BaseCommand):
                 else:
                 else:
                     thread_replies = random.randint(0, 10)
                     thread_replies = random.randint(0, 10)
 
 
-                for x in range(thread_replies):
+                for _ in range(thread_replies):
                     datetime = timezone.now()
                     datetime = timezone.now()
                     user = UserModel.objects.order_by('?')[:1][0]
                     user = UserModel.objects.order_by('?')[:1][0]
 
 
@@ -168,7 +166,7 @@ class Command(BaseCommand):
 
 
         pinned_threads = random.randint(0, int(created_threads * 0.025)) or 1
         pinned_threads = random.randint(0, int(created_threads * 0.025)) or 1
         self.stdout.write('\nPinning %s threads...' % pinned_threads)
         self.stdout.write('\nPinning %s threads...' % pinned_threads)
-        for i in range(0, pinned_threads):
+        for _ in range(0, pinned_threads):
             thread = Thread.objects.order_by('?')[:1][0]
             thread = Thread.objects.order_by('?')[:1][0]
             if random.randint(0, 100) > 75:
             if random.randint(0, 100) > 75:
                 thread.weight = 2
                 thread.weight = 2
@@ -193,7 +191,7 @@ class Command(BaseCommand):
         else:
         else:
             paragraphs_to_make = random.randint(1, 5)
             paragraphs_to_make = random.randint(1, 5)
 
 
-        for i in range(paragraphs_to_make):
+        for _ in range(paragraphs_to_make):
             if random.randint(0, 100) > 95:
             if random.randint(0, 100) > 95:
                 cat_width = random.randint(1, 16) * random.choice([100, 90, 80])
                 cat_width = random.randint(1, 16) * random.choice([100, 90, 80])
                 cat_height = random.randint(1, 12) * random.choice([100, 90, 80])
                 cat_height = random.randint(1, 12) * random.choice([100, 90, 80])

+ 0 - 2
misago/faker/management/commands/createfakeusers.py

@@ -1,5 +1,4 @@
 import random
 import random
-import sys
 import time
 import time
 
 
 from faker import Factory
 from faker import Factory
@@ -8,7 +7,6 @@ from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.management.base import BaseCommand
 from django.core.management.base import BaseCommand
 from django.db import IntegrityError
 from django.db import IntegrityError
-from django.utils.six.moves import range
 
 
 from misago.core.management.progressbar import show_progress
 from misago.core.management.progressbar import show_progress
 from misago.users.avatars import dynamic, gallery
 from misago.users.avatars import dynamic, gallery

+ 1 - 1
misago/legal/migrations/0001_initial.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 
 
 from misago.conf.migrationutils import migrate_settings_group
 from misago.conf.migrationutils import migrate_settings_group
 
 

+ 0 - 1
misago/markup/api.py

@@ -4,7 +4,6 @@ from rest_framework.response import Response
 
 
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.utils import six
 from django.utils import six
-from django.utils.translation import ugettext as _
 
 
 from misago.threads.validators import validate_post
 from misago.threads.validators import validate_post
 
 

+ 1 - 1
misago/markup/mentions.py

@@ -1,6 +1,6 @@
 import re
 import re
 
 
-from bs4 import BeautifulSoup, NavigableString
+from bs4 import BeautifulSoup
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.utils import six
 from django.utils import six

+ 0 - 1
misago/markup/parser.py

@@ -150,7 +150,6 @@ def linkify_paragraphs(result):
 
 
 def clean_links(request, result, force_shva=False):
 def clean_links(request, result, force_shva=False):
     host = request.get_host()
     host = request.get_host()
-    site_address = '%s://%s' % (request.scheme, request.get_host())
 
 
     soup = BeautifulSoup(result['parsed_text'], 'html5lib')
     soup = BeautifulSoup(result['parsed_text'], 'html5lib')
     for link in soup.find_all('a'):
     for link in soup.find_all('a'):

+ 1 - 1
misago/readtracker/apps.py

@@ -7,4 +7,4 @@ class MisagoReadTrackerConfig(AppConfig):
     verbose_name = "Misago Read Tracker"
     verbose_name = "Misago Read Tracker"
 
 
     def ready(self):
     def ready(self):
-        from . import signals
+        from . import signals as _

+ 0 - 3
misago/readtracker/models.py

@@ -1,8 +1,5 @@
-from datetime import timedelta
-
 from django.conf import settings
 from django.conf import settings
 from django.db import models
 from django.db import models
-from django.utils import timezone
 
 
 
 
 class CategoryRead(models.Model):
 class CategoryRead(models.Model):

+ 2 - 1
misago/readtracker/tests/test_readtracker.py

@@ -93,7 +93,8 @@ class CategoriesTrackerTests(ReadTrackerTests):
         categoriestracker.make_read_aware(self.user, self.categories)
         categoriestracker.make_read_aware(self.user, self.categories)
         self.assertTrue(self.category.is_read)
         self.assertTrue(self.category.is_read)
 
 
-        thread = self.post_thread(self.user.joined_on + timedelta(days=1))
+        self.post_thread(self.user.joined_on + timedelta(days=1))
+
         categoriestracker.sync_record(self.user, self.category)
         categoriestracker.sync_record(self.user, self.category)
         categoriestracker.make_read_aware(self.user, self.categories)
         categoriestracker.make_read_aware(self.user, self.categories)
         self.assertFalse(self.category.is_read)
         self.assertFalse(self.category.is_read)

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

@@ -2,7 +2,6 @@ from rest_framework.response import Response
 
 
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
 from django.db.models import F
 from django.db.models import F
-from django.http import Http404
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _

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

@@ -1,4 +1,3 @@
-from rest_framework import serializers
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
@@ -6,7 +5,6 @@ from django.utils.translation import ugettext as _
 from django.utils.translation import ungettext
 from django.utils.translation import ungettext
 
 
 from misago.conf import settings
 from misago.conf import settings
-from misago.threads.events import record_event
 from misago.threads.models import Thread
 from misago.threads.models import Thread
 from misago.threads.moderation import threads as moderation
 from misago.threads.moderation import threads as moderation
 from misago.threads.permissions import exclude_invisible_posts
 from misago.threads.permissions import exclude_invisible_posts

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

@@ -7,7 +7,7 @@ from misago.acl import add_acl
 from misago.conf import settings
 from misago.conf import settings
 from misago.threads.serializers import AttachmentSerializer
 from misago.threads.serializers import AttachmentSerializer
 
 
-from . import PostingEndpoint, PostingInterrupt, PostingMiddleware
+from . import PostingEndpoint, PostingMiddleware
 
 
 
 
 class AttachmentsMiddleware(PostingMiddleware):
 class AttachmentsMiddleware(PostingMiddleware):

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

@@ -1,9 +1,7 @@
 from rest_framework import serializers
 from rest_framework import serializers
 
 
-from django.db.models import F
 from django.utils.translation import ugettext_lazy
 from django.utils.translation import ugettext_lazy
 
 
-from misago.conf import settings
 from misago.markup import common_flavour
 from misago.markup import common_flavour
 from misago.threads.checksums import update_post_checksum
 from misago.threads.checksums import update_post_checksum
 from misago.threads.validators import validate_post, validate_title
 from misago.threads.validators import validate_post, validate_title

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

@@ -37,8 +37,7 @@ class SubscribeMiddleware(PostingMiddleware):
             return
             return
 
 
         try:
         try:
-            subscription = self.user.subscription_set.get(thread=self.thread)
-            return
+            return self.user.subscription_set.get(thread=self.thread)
         except Subscription.DoesNotExist:
         except Subscription.DoesNotExist:
             pass
             pass
 
 

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

@@ -1,6 +1,6 @@
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
-from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.exceptions import PermissionDenied
 from django.http import Http404
 from django.http import Http404
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 from django.utils.translation import ungettext
 from django.utils.translation import ungettext

+ 2 - 3
misago/threads/api/threadendpoints/patch.py

@@ -10,7 +10,6 @@ from misago.categories.permissions import allow_browse_category, allow_see_categ
 from misago.categories.serializers import CategorySerializer
 from misago.categories.serializers import CategorySerializer
 from misago.core.apipatch import ApiPatch
 from misago.core.apipatch import ApiPatch
 from misago.core.shortcuts import get_int_or_404
 from misago.core.shortcuts import get_int_or_404
-from misago.threads.models import ThreadParticipant
 from misago.threads.moderation import threads as moderation
 from misago.threads.moderation import threads as moderation
 from misago.threads.participants import (
 from misago.threads.participants import (
     add_participant, change_owner, make_participants_aware, remove_participant)
     add_participant, change_owner, make_participants_aware, remove_participant)
@@ -122,7 +121,7 @@ def patch_flatten_categories(request, thread, value):
             'category': thread.category_id,
             'category': thread.category_id,
             'top_category': thread.top_category.pk,
             'top_category': thread.top_category.pk,
         }
         }
-    except AttributeError as e:
+    except AttributeError:
         return {
         return {
             'category': thread.category_id,
             'category': thread.category_id,
             'top_category': None
             'top_category': None
@@ -299,7 +298,7 @@ def thread_patch_endpoint(request, thread):
     unapproved_changed = old_is_unapproved != thread.is_unapproved
     unapproved_changed = old_is_unapproved != thread.is_unapproved
     category_changed = old_category != thread.category
     category_changed = old_category != thread.category
 
 
-    title_changed = old_is_hidden != thread.is_hidden
+    title_changed = old_title != thread.title
     if thread.category.last_thread_id != thread.pk:
     if thread.category.last_thread_id != thread.pk:
         title_changed = False # don't trigger resync on simple title change
         title_changed = False # don't trigger resync on simple title change
 
 

+ 1 - 1
misago/threads/apps.py

@@ -7,4 +7,4 @@ class MisagoThreadsConfig(AppConfig):
     verbose_name = "Misago Threads"
     verbose_name = "Misago Threads"
 
 
     def ready(self):
     def ready(self):
-        from . import signals
+        from . import signals as _

+ 1 - 1
misago/threads/forms.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
-from .models import Attachment, AttachmentType
+from .models import AttachmentType
 
 
 
 
 def get_searchable_filetypes():
 def get_searchable_filetypes():

+ 0 - 1
misago/threads/middleware.py

@@ -3,7 +3,6 @@ from django.utils.deprecation import MiddlewareMixin
 from misago.categories.models import Category
 from misago.categories.models import Category
 
 
 from .models import Thread
 from .models import Thread
-from .permissions import exclude_invisible_threads
 from .viewmodels import filter_read_threads_queryset
 from .viewmodels import filter_read_threads_queryset
 
 
 
 

+ 1 - 1
misago/threads/migrations/0001_initial.py

@@ -9,7 +9,7 @@ from django.contrib.postgres.search import SearchVectorField
 from django.db import migrations, models
 from django.db import migrations, models
 
 
 import misago.threads.models.attachment
 import misago.threads.models.attachment
-from misago.core.pgutils import CreatePartialCompositeIndex, CreatePartialIndex
+from misago.core.pgutils import CreatePartialIndex
 
 
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):

+ 1 - 2
misago/threads/migrations/0002_threads_settings.py

@@ -1,8 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.conf import settings
-from django.db import migrations, models
+from django.db import migrations
 
 
 from misago.conf.migrationutils import migrate_settings_group
 from misago.conf.migrationutils import migrate_settings_group
 
 

+ 1 - 2
misago/threads/migrations/0003_attachment_types.py

@@ -2,8 +2,7 @@
 # Generated by Django 1.9.7 on 2016-10-04 21:41
 # Generated by Django 1.9.7 on 2016-10-04 21:41
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.conf import settings
-from django.db import migrations, models
+from django.db import migrations
 
 
 
 
 ATTACHMENTS = (
 ATTACHMENTS = (

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

@@ -5,15 +5,12 @@ import copy
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.search import SearchVector, SearchVectorField
 from django.contrib.postgres.search import SearchVector, SearchVectorField
 from django.db import models
 from django.db import models
-from django.dispatch import receiver
-from django.urls import reverse
 from django.utils import six, timezone
 from django.utils import six, timezone
 from django.utils.encoding import python_2_unicode_compatible
 from django.utils.encoding import python_2_unicode_compatible
 
 
 from misago.conf import settings
 from misago.conf import settings
 from misago.core.utils import parse_iso8601_string
 from misago.core.utils import parse_iso8601_string
 from misago.markup import finalise_markup
 from misago.markup import finalise_markup
-from misago.threads import threadtypes
 from misago.threads.checksums import is_post_valid, update_post_checksum
 from misago.threads.checksums import is_post_valid, update_post_checksum
 
 
 
 
@@ -100,6 +97,9 @@ class Post(models.Model):
         super(Post, self).delete(*args, **kwargs)
         super(Post, self).delete(*args, **kwargs)
 
 
     def merge(self, other_post):
     def merge(self, other_post):
+        if not self.poster_id or self.poster_id != other_post.poster_id:
+            raise ValueError("post can't be merged with other user's post")
+
         if self.thread_id != other_post.thread_id:
         if self.thread_id != other_post.thread_id:
             raise ValueError("only posts belonging to same thread can be merged")
             raise ValueError("only posts belonging to same thread can be merged")
 
 

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

@@ -1,5 +1,5 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
-from django.db import models, transaction
+from django.db import models
 from django.utils.encoding import python_2_unicode_compatible
 from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 

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

@@ -1,6 +1,5 @@
 from django.db.transaction import atomic
 from django.db.transaction import atomic
 from django.utils import timezone
 from django.utils import timezone
-from django.utils.translation import ugettext as _
 
 
 from misago.threads.events import record_event
 from misago.threads.events import record_event
 
 

+ 0 - 3
misago/threads/paginator.py

@@ -1,7 +1,4 @@
-from math import ceil, floor
-
 from django.core.paginator import Paginator
 from django.core.paginator import Paginator
-from django.utils.functional import cached_property
 
 
 
 
 class PostsPaginator(Paginator):
 class PostsPaginator(Paginator):

+ 0 - 1
misago/threads/participants.py

@@ -1,7 +1,6 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
-from misago.core import deprecations
 from misago.core.mail import build_mail, send_messages
 from misago.core.mail import build_mail, send_messages
 
 
 from .events import record_event
 from .events import record_event

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

@@ -1,11 +1,9 @@
 from django import forms
 from django import forms
-from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
-from django.db.models import Q
 from django.http import Http404
 from django.http import Http404
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 
-from misago.acl import add_acl, algebra
+from misago.acl import algebra
 from misago.acl.decorators import return_boolean
 from misago.acl.decorators import return_boolean
 from misago.acl.models import Role
 from misago.acl.models import Role
 from misago.categories import PRIVATE_THREADS_ROOT_NAME
 from misago.categories import PRIVATE_THREADS_ROOT_NAME

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

@@ -9,7 +9,7 @@ from django.utils.translation import ungettext
 from misago.acl import add_acl, algebra
 from misago.acl import add_acl, algebra
 from misago.acl.decorators import return_boolean
 from misago.acl.decorators import return_boolean
 from misago.acl.models import Role
 from misago.acl.models import Role
-from misago.categories.models import Category, CategoryRole, RoleCategoryACL
+from misago.categories.models import Category, CategoryRole
 from misago.categories.permissions import get_categories_roles
 from misago.categories.permissions import get_categories_roles
 from misago.core.forms import YesNoSwitch
 from misago.core.forms import YesNoSwitch
 from misago.threads.models import Post, Thread
 from misago.threads.models import Post, Thread

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

@@ -2,7 +2,6 @@ from rest_framework import serializers
 
 
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.core.utils import format_plaintext_for_html
 from misago.threads.models import Attachment
 from misago.threads.models import Attachment
 
 
 
 

+ 0 - 2
misago/threads/serializers/feed.py

@@ -1,7 +1,5 @@
 from rest_framework import serializers
 from rest_framework import serializers
 
 
-from django.urls import reverse
-
 from misago.categories.serializers import CategorySerializer
 from misago.categories.serializers import CategorySerializer
 from misago.core.serializers import MutableFields
 from misago.core.serializers import MutableFields
 from misago.threads.models import Post
 from misago.threads.models import Post

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

@@ -2,7 +2,6 @@ from rest_framework import serializers
 
 
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.categories.serializers import CategorySerializer
 from misago.core.serializers import MutableFields
 from misago.core.serializers import MutableFields
 from misago.threads.models import Post
 from misago.threads.models import Post
 from misago.users.serializers import UserSerializer as BaseUserSerializer
 from misago.users.serializers import UserSerializer as BaseUserSerializer

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

@@ -4,8 +4,6 @@ from django import template
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 from django.utils.translation import ngettext
 from django.utils.translation import ngettext
 
 
-from misago.conf import settings
-
 
 
 register = template.Library()
 register = template.Library()
 
 

+ 7 - 7
misago/threads/tests/test_attachments_api.py

@@ -104,7 +104,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_type_is_locked(self):
     def test_type_is_locked(self):
         """new uploads for this filetype are locked"""
         """new uploads for this filetype are locked"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='png',
             extensions='png',
             mimetypes='application/pdf',
             mimetypes='application/pdf',
@@ -119,7 +119,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_type_is_disabled(self):
     def test_type_is_disabled(self):
         """new uploads for this filetype are disabled"""
         """new uploads for this filetype are disabled"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='png',
             extensions='png',
             mimetypes='application/pdf',
             mimetypes='application/pdf',
@@ -168,7 +168,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_corrupted_image_upload(self):
     def test_corrupted_image_upload(self):
         """corrupted image upload is handled"""
         """corrupted image upload is handled"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='gif'
             extensions='gif'
         )
         )
@@ -181,7 +181,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_document_upload(self):
     def test_document_upload(self):
         """successful upload creates orphan attachment"""
         """successful upload creates orphan attachment"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='pdf',
             extensions='pdf',
             mimetypes='application/pdf'
             mimetypes='application/pdf'
@@ -220,7 +220,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_small_image_upload(self):
     def test_small_image_upload(self):
         """successful small image upload creates orphan attachment without thumbnail"""
         """successful small image upload creates orphan attachment without thumbnail"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='jpeg,jpg',
             extensions='jpeg,jpg',
             mimetypes='image/jpeg'
             mimetypes='image/jpeg'
@@ -257,7 +257,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
             'max_attachment_size': 10 * 1024
             'max_attachment_size': 10 * 1024
         })
         })
 
 
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='png',
             extensions='png',
             mimetypes='image/png'
             mimetypes='image/png'
@@ -308,7 +308,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_animated_image_upload(self):
     def test_animated_image_upload(self):
         """successful gif upload creates orphan attachment with thumbnail"""
         """successful gif upload creates orphan attachment with thumbnail"""
-        attachment_type = AttachmentType.objects.create(
+        AttachmentType.objects.create(
             name="Test extension",
             name="Test extension",
             extensions='gif',
             extensions='gif',
             mimetypes='image/gif'
             mimetypes='image/gif'

+ 3 - 3
misago/threads/tests/test_clearattachments.py

@@ -31,7 +31,7 @@ class ClearAttachmentsTests(TestCase):
         cutoff = timezone.now() - timedelta(minutes=settings.MISAGO_ATTACHMENT_ORPHANED_EXPIRE)
         cutoff = timezone.now() - timedelta(minutes=settings.MISAGO_ATTACHMENT_ORPHANED_EXPIRE)
         cutoff -= timedelta(minutes=5)
         cutoff -= timedelta(minutes=5)
 
 
-        for i in range(5):
+        for _ in range(5):
             Attachment.objects.create(
             Attachment.objects.create(
                 secret=Attachment.generate_new_secret(),
                 secret=Attachment.generate_new_secret(),
                 filetype=filetype,
                 filetype=filetype,
@@ -47,7 +47,7 @@ class ClearAttachmentsTests(TestCase):
         category = Category.objects.get(slug='first-category')
         category = Category.objects.get(slug='first-category')
         post = testutils.post_thread(category).first_post
         post = testutils.post_thread(category).first_post
 
 
-        for i in range(5):
+        for _ in range(5):
             Attachment.objects.create(
             Attachment.objects.create(
                 secret=Attachment.generate_new_secret(),
                 secret=Attachment.generate_new_secret(),
                 filetype=filetype,
                 filetype=filetype,
@@ -61,7 +61,7 @@ class ClearAttachmentsTests(TestCase):
             )
             )
 
 
         # create 5 fresh orphaned attachments
         # create 5 fresh orphaned attachments
-        for i in range(5):
+        for _ in range(5):
             Attachment.objects.create(
             Attachment.objects.create(
                 secret=Attachment.generate_new_secret(),
                 secret=Attachment.generate_new_secret(),
                 filetype=filetype,
                 filetype=filetype,

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

@@ -1,6 +1,4 @@
 #-*- coding: utf-8 -*-
 #-*- coding: utf-8 -*-
-import random
-
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.test import TestCase
 from django.utils import timezone
 from django.utils import timezone
@@ -8,8 +6,7 @@ from django.utils import timezone
 from misago.acl import add_acl
 from misago.acl import add_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads.events import record_event
 from misago.threads.events import record_event
-from misago.threads.models import Post, Thread
-from misago.threads.testutils import reply_thread
+from misago.threads.models import Thread
 
 
 
 
 UserModel = get_user_model()
 UserModel = get_user_model()

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

@@ -4,8 +4,7 @@ from django.utils import timezone
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.threads.api.postingendpoint import PostingInterrupt
 from misago.threads.api.postingendpoint import PostingInterrupt
-from misago.threads.api.postingendpoint.floodprotection import (
-    MIN_POSTING_PAUSE, FloodProtectionMiddleware)
+from misago.threads.api.postingendpoint.floodprotection import FloodProtectionMiddleware
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 

+ 12 - 12
misago/threads/tests/test_gotoviews.py

@@ -32,7 +32,7 @@ class GotoPostTests(GotoViewTestCase):
 
 
     def test_goto_last_post_on_page(self):
     def test_goto_last_post_on_page(self):
         """last post on page redirect url is valid"""
         """last post on page redirect url is valid"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
 
 
         response = self.client.get(post.get_absolute_url())
         response = self.client.get(post.get_absolute_url())
@@ -44,7 +44,7 @@ class GotoPostTests(GotoViewTestCase):
 
 
     def test_goto_first_post_on_next_page(self):
     def test_goto_first_post_on_next_page(self):
         """first post on next page redirect url is valid"""
         """first post on next page redirect url is valid"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
 
 
         response = self.client.get(post.get_absolute_url())
         response = self.client.get(post.get_absolute_url())
@@ -57,7 +57,7 @@ class GotoPostTests(GotoViewTestCase):
     def test_goto_first_post_on_page_three_out_of_five(self):
     def test_goto_first_post_on_page_three_out_of_five(self):
         """first post on next page redirect url is valid"""
         """first post on next page redirect url is valid"""
         posts = []
         posts = []
-        for i in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
             posts.append(post)
             posts.append(post)
 
 
@@ -73,7 +73,7 @@ class GotoPostTests(GotoViewTestCase):
     def test_goto_first_event_on_page_three_out_of_five(self):
     def test_goto_first_event_on_page_three_out_of_five(self):
         """event redirect url is valid"""
         """event redirect url is valid"""
         posts = []
         posts = []
-        for i in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE * 4 - 1):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
             posts.append(post)
             posts.append(post)
 
 
@@ -105,7 +105,7 @@ class GotoLastTests(GotoViewTestCase):
 
 
     def test_goto_last_post_on_page(self):
     def test_goto_last_post_on_page(self):
         """last post on page redirect url is valid"""
         """last post on page redirect url is valid"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
 
 
         response = self.client.get(self.thread.get_last_post_url())
         response = self.client.get(self.thread.get_last_post_url())
@@ -129,7 +129,7 @@ class GotoNewTests(GotoViewTestCase):
         read_thread(self.user, self.thread, self.thread.last_post)
         read_thread(self.user, self.thread, self.thread.last_post)
 
 
         post = testutils.reply_thread(self.thread, posted_on=timezone.now())
         post = testutils.reply_thread(self.thread, posted_on=timezone.now())
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         response = self.client.get(self.thread.get_new_post_url())
         response = self.client.get(self.thread.get_new_post_url())
@@ -138,14 +138,14 @@ class GotoNewTests(GotoViewTestCase):
 
 
     def test_goto_first_new_post_on_next_page(self):
     def test_goto_first_new_post_on_next_page(self):
         """first unread post redirect url in already read multipage thread is valid"""
         """first unread post redirect url in already read multipage thread is valid"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         make_thread_read_aware(self.user, self.thread)
         make_thread_read_aware(self.user, self.thread)
         read_thread(self.user, self.thread, self.thread.last_post)
         read_thread(self.user, self.thread, self.thread.last_post)
 
 
         post = testutils.reply_thread(self.thread, posted_on=timezone.now())
         post = testutils.reply_thread(self.thread, posted_on=timezone.now())
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         response = self.client.get(self.thread.get_new_post_url())
         response = self.client.get(self.thread.get_new_post_url())
@@ -154,7 +154,7 @@ class GotoNewTests(GotoViewTestCase):
 
 
     def test_goto_first_new_post_in_read_thread(self):
     def test_goto_first_new_post_in_read_thread(self):
         """goto new in read thread points to last post"""
         """goto new in read thread points to last post"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
             post = testutils.reply_thread(self.thread, posted_on=timezone.now())
             post = testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         make_thread_read_aware(self.user, self.thread)
         make_thread_read_aware(self.user, self.thread)
@@ -166,7 +166,7 @@ class GotoNewTests(GotoViewTestCase):
 
 
     def test_guest_goto_first_new_post_in_thread(self):
     def test_guest_goto_first_new_post_in_thread(self):
         """guest goto new in read thread points to last post"""
         """guest goto new in read thread points to last post"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
             post = testutils.reply_thread(self.thread, posted_on=timezone.now())
             post = testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         self.logout_user()
         self.logout_user()
@@ -201,14 +201,14 @@ class GotoUnapprovedTests(GotoViewTestCase):
 
 
     def test_vie_handles_unapproved_posts(self):
     def test_vie_handles_unapproved_posts(self):
         """if thread has unapproved posts, redirect to first of them"""
         """if thread has unapproved posts, redirect to first of them"""
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         make_thread_read_aware(self.user, self.thread)
         make_thread_read_aware(self.user, self.thread)
         read_thread(self.user, self.thread, self.thread.last_post)
         read_thread(self.user, self.thread, self.thread.last_post)
 
 
         post = testutils.reply_thread(self.thread, is_unapproved=True, posted_on=timezone.now())
         post = testutils.reply_thread(self.thread, is_unapproved=True, posted_on=timezone.now())
-        for i in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
+        for _ in range(settings.MISAGO_POSTS_PER_PAGE + settings.MISAGO_POSTS_TAIL - 1):
             testutils.reply_thread(self.thread, posted_on=timezone.now())
             testutils.reply_thread(self.thread, posted_on=timezone.now())
 
 
         self.grant_permission()
         self.grant_permission()

+ 17 - 2
misago/threads/tests/test_post_model.py

@@ -70,6 +70,21 @@ class PostModelTests(TestCase):
             last_poster_slug='tester'
             last_poster_slug='tester'
         )
         )
 
 
+        # can't merge with other users posts
+        with self.assertRaises(ValueError):
+            self.post.merge(Post.objects.create(
+                category=self.category,
+                thread=self.thread,
+                poster=other_user,
+                poster_name=other_user.username,
+                poster_ip='127.0.0.1',
+                original="Hello! I am test message!",
+                parsed="<p>Hello! I am test message!</p>",
+                checksum="nope",
+                posted_on=timezone.now() + timedelta(minutes=5),
+                updated_on=timezone.now() + timedelta(minutes=5),
+            ))
+
         # can't merge across threads
         # can't merge across threads
         with self.assertRaises(ValueError):
         with self.assertRaises(ValueError):
             self.post.merge(Post.objects.create(
             self.post.merge(Post.objects.create(
@@ -82,7 +97,7 @@ class PostModelTests(TestCase):
                 parsed="<p>Hello! I am test message!</p>",
                 parsed="<p>Hello! I am test message!</p>",
                 checksum="nope",
                 checksum="nope",
                 posted_on=timezone.now() + timedelta(minutes=5),
                 posted_on=timezone.now() + timedelta(minutes=5),
-                updated_on=timezone.now() + timedelta(minutes=5)
+                updated_on=timezone.now() + timedelta(minutes=5),
             ))
             ))
 
 
         # can't merge with events
         # can't merge with events
@@ -98,7 +113,7 @@ class PostModelTests(TestCase):
                 checksum="nope",
                 checksum="nope",
                 posted_on=timezone.now() + timedelta(minutes=5),
                 posted_on=timezone.now() + timedelta(minutes=5),
                 updated_on=timezone.now() + timedelta(minutes=5),
                 updated_on=timezone.now() + timedelta(minutes=5),
-                is_event=True
+                is_event=True,
             ))
             ))
 
 
     def test_merge(self):
     def test_merge(self):

+ 3 - 3
misago/threads/tests/test_privatethread_patch_api.py

@@ -131,7 +131,7 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
         """adding user to thread add user to thread as participant, sets event and emails him"""
         """adding user to thread add user to thread as participant, sets event and emails him"""
         ThreadParticipant.objects.set_owner(self.thread, self.user)
         ThreadParticipant.objects.set_owner(self.thread, self.user)
 
 
-        response = self.patch(self.api_link, [
+        self.patch(self.api_link, [
             {'op': 'add', 'path': 'participants', 'value': self.other_user.username}
             {'op': 'add', 'path': 'participants', 'value': self.other_user.username}
         ])
         ])
 
 
@@ -158,7 +158,7 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
             'can_moderate_private_threads': 1
             'can_moderate_private_threads': 1
         })
         })
 
 
-        response = self.patch(self.api_link, [
+        self.patch(self.api_link, [
             {'op': 'add', 'path': 'participants', 'value': self.user.username}
             {'op': 'add', 'path': 'participants', 'value': self.user.username}
         ])
         ])
 
 
@@ -181,7 +181,7 @@ class PrivateThreadAddParticipantApiTests(PrivateThreadPatchApiTestCase):
             'can_moderate_private_threads': 1
             'can_moderate_private_threads': 1
         })
         })
 
 
-        response = self.patch(self.api_link, [
+        self.patch(self.api_link, [
             {'op': 'add', 'path': 'participants', 'value': self.other_user.username}
             {'op': 'add', 'path': 'participants', 'value': self.other_user.username}
         ])
         ])
 
 

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

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 
 
-from misago.acl.testutils import override_acl
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.threads.models import ThreadParticipant
 from misago.threads.models import ThreadParticipant
 
 

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

@@ -1,8 +1,6 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-import json
-
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.core import mail
 from django.core import mail
 from django.urls import reverse
 from django.urls import reverse
@@ -10,7 +8,7 @@ from django.utils.encoding import smart_str
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
-from misago.threads.models import Thread, ThreadParticipant
+from misago.threads.models import ThreadParticipant
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 

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

@@ -40,9 +40,11 @@ class PrivateThreadsListApiTests(PrivateThreadsTestCase):
     def test_thread_visibility(self):
     def test_thread_visibility(self):
         """only participated threads are returned by private threads api"""
         """only participated threads are returned by private threads api"""
         visible = testutils.post_thread(category=self.category, poster=self.user)
         visible = testutils.post_thread(category=self.category, poster=self.user)
-        hidden = testutils.post_thread(category=self.category, poster=self.user)
         reported = testutils.post_thread(category=self.category, poster=self.user)
         reported = testutils.post_thread(category=self.category, poster=self.user)
 
 
+        # hidden thread
+        testutils.post_thread(category=self.category, poster=self.user)
+
         ThreadParticipant.objects.add_participants(visible, [self.user])
         ThreadParticipant.objects.add_participants(visible, [self.user])
 
 
         reported.has_reported_posts = True
         reported.has_reported_posts = True

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

@@ -38,9 +38,11 @@ class PrivateThreadsListTests(PrivateThreadsTestCase):
     def test_thread_visibility(self):
     def test_thread_visibility(self):
         """only participated threads are returned by private threads view"""
         """only participated threads are returned by private threads view"""
         visible = testutils.post_thread(category=self.category, poster=self.user)
         visible = testutils.post_thread(category=self.category, poster=self.user)
-        hidden = testutils.post_thread(category=self.category, poster=self.user)
         reported = testutils.post_thread(category=self.category, poster=self.user)
         reported = testutils.post_thread(category=self.category, poster=self.user)
 
 
+        # post hidden thread
+        testutils.post_thread(category=self.category, poster=self.user)
+
         ThreadParticipant.objects.add_participants(visible, [self.user])
         ThreadParticipant.objects.add_participants(visible, [self.user])
 
 
         reported.has_reported_posts = True
         reported.has_reported_posts = True

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

@@ -37,7 +37,7 @@ class SubscriptionsTests(TestCase):
     def test_anon_threads_subscription(self):
     def test_anon_threads_subscription(self):
         """make multiple threads list sub aware for anon"""
         """make multiple threads list sub aware for anon"""
         threads = []
         threads = []
-        for i in range(10):
+        for _ in range(10):
             threads.append(
             threads.append(
                 self.post_thread(timezone.now() - timedelta(days=10)))
                 self.post_thread(timezone.now() - timedelta(days=10)))
 
 
@@ -51,18 +51,6 @@ class SubscriptionsTests(TestCase):
         make_subscription_aware(self.user, self.thread)
         make_subscription_aware(self.user, self.thread)
         self.assertIsNone(self.thread.subscription)
         self.assertIsNone(self.thread.subscription)
 
 
-    def test_threads_no_subscription(self):
-        """make mulitple threads sub aware for authenticated"""
-        threads = []
-        for i in range(10):
-            threads.append(
-                self.post_thread(timezone.now() - timedelta(days=10)))
-
-        make_subscription_aware(self.user, threads)
-
-        for thread in threads:
-            self.assertIsNone(thread.subscription)
-
     def test_subscribed_thread(self):
     def test_subscribed_thread(self):
         """make thread sub aware for authenticated"""
         """make thread sub aware for authenticated"""
         self.user.subscription_set.create(
         self.user.subscription_set.create(

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

@@ -23,9 +23,9 @@ class SynchronizeThreadsTests(TestCase):
         """command synchronizes threads"""
         """command synchronizes threads"""
         category = Category.objects.all_categories()[:1][0]
         category = Category.objects.all_categories()[:1][0]
 
 
-        threads = [testutils.post_thread(category) for t in range(10)]
+        threads = [testutils.post_thread(category) for _ in range(10)]
         for i, thread in enumerate(threads):
         for i, thread in enumerate(threads):
-            [testutils.reply_thread(thread) for r in range(i)]
+            [testutils.reply_thread(thread) for _ in range(i)]
             thread.replies = 0
             thread.replies = 0
             thread.save()
             thread.save()
 
 

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

@@ -10,7 +10,6 @@ from django.utils.encoding import smart_str
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.models import Thread
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -197,8 +196,6 @@ class EditReplyTests(AuthenticatedUserTestCase):
         })
         })
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        thread = Thread.objects.get(pk=self.thread.pk)
-
         self.override_acl()
         self.override_acl()
         response = self.client.get(self.thread.get_absolute_url())
         response = self.client.get(self.thread.get_absolute_url())
         self.assertContains(response, self.post.parsed)
         self.assertContains(response, self.post.parsed)
@@ -222,8 +219,6 @@ class EditReplyTests(AuthenticatedUserTestCase):
         })
         })
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        thread = Thread.objects.get(pk=self.thread.pk)
-
         self.override_acl()
         self.override_acl()
         response = self.client.get(self.thread.get_absolute_url())
         response = self.client.get(self.thread.get_absolute_url())
         self.assertContains(response, "<p>This is test edit!</p>")
         self.assertContains(response, "<p>This is test edit!</p>")
@@ -262,7 +257,7 @@ class EditReplyTests(AuthenticatedUserTestCase):
             'pk': self.thread.first_post.pk
             'pk': self.thread.first_post.pk
         })
         })
 
 
-        response = self.put(self.api_link, data={
+        response = self.put(api_link, data={
             'post': "This is test edit!"
             'post': "This is test edit!"
         })
         })
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 5 - 7
misago/threads/tests/test_thread_merge_api.py

@@ -1,7 +1,4 @@
-import json
-
 from django.urls import reverse
 from django.urls import reverse
-from django.utils.encoding import smart_str
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
@@ -284,8 +281,9 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         })
         })
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+
+        testutils.post_poll(self.thread, self.user)
+        testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'thread_url': other_thread.get_absolute_url(),
             'thread_url': other_thread.get_absolute_url(),
@@ -311,8 +309,8 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
         })
         })
 
 
         other_thread = testutils.post_thread(self.category_b)
         other_thread = testutils.post_thread(self.category_b)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+        testutils.post_poll(self.thread, self.user)
+        testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(self.api_link, {
         response = self.client.post(self.api_link, {
             'thread_url': other_thread.get_absolute_url(),
             'thread_url': other_thread.get_absolute_url(),

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

@@ -1,15 +1,9 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-import json
-from datetime import timedelta
-
 from django.urls import reverse
 from django.urls import reverse
 
 
-from misago.acl.testutils import override_acl
-from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.models import Post
 
 
 from .test_threads_api import ThreadsApiTestCase
 from .test_threads_api import ThreadsApiTestCase
 
 

+ 6 - 9
misago/threads/tests/test_thread_postmerge_api.py

@@ -2,11 +2,8 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 import json
 import json
-from datetime import timedelta
 
 
 from django.urls import reverse
 from django.urls import reverse
-from django.utils import timezone
-from django.utils.encoding import smart_str
 from django.utils.six.moves import range
 from django.utils.six.moves import range
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
@@ -223,8 +220,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
 
 
     def test_merge_posts(self):
     def test_merge_posts(self):
         """api merges two posts"""
         """api merges two posts"""
-        post_a = testutils.reply_thread(self.thread, poster="Bob", message="Battęry")
-        post_b = testutils.reply_thread(self.thread, poster="Bob", message="Hórse")
+        post_a = testutils.reply_thread(self.thread, poster=self.user, message="Battęry")
+        post_b = testutils.reply_thread(self.thread, poster=self.user, message="Hórse")
 
 
         thread_replies = self.thread.replies
         thread_replies = self.thread.replies
 
 
@@ -250,8 +247,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
 
 
         response = self.client.post(self.api_link, json.dumps({
         response = self.client.post(self.api_link, json.dumps({
             'posts': [
             'posts': [
-                testutils.reply_thread(self.thread, poster="Bob", is_hidden=True).pk,
-                testutils.reply_thread(self.thread, poster="Bob", is_hidden=True).pk
+                testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk,
+                testutils.reply_thread(self.thread, poster=self.user, is_hidden=True).pk
             ]
             ]
         }), content_type="application/json")
         }), content_type="application/json")
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -264,8 +261,8 @@ class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
 
 
         response = self.client.post(self.api_link, json.dumps({
         response = self.client.post(self.api_link, json.dumps({
             'posts': [
             'posts': [
-                testutils.reply_thread(self.thread, poster="Bob", is_unapproved=True).pk,
-                testutils.reply_thread(self.thread, poster="Bob", is_unapproved=True).pk
+                testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk,
+                testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True).pk
             ]
             ]
         }), content_type="application/json")
         }), content_type="application/json")
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

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

@@ -10,7 +10,7 @@ from django.utils import timezone
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.models import Post, Thread
+from misago.threads.models import Post
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 

+ 0 - 1
misago/threads/tests/test_thread_postread_api.py

@@ -2,7 +2,6 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils import timezone
 
 
 from misago.threads import testutils
 from misago.threads import testutils
-from misago.threads.models import Post, Thread
 
 
 from .test_threads_api import ThreadsApiTestCase
 from .test_threads_api import ThreadsApiTestCase
 
 

+ 0 - 5
misago/threads/tests/test_thread_start_api.py

@@ -4,10 +4,7 @@ from __future__ import unicode_literals
 from django.urls import reverse
 from django.urls import reverse
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
-from misago.categories import THREADS_ROOT_NAME
 from misago.categories.models import Category
 from misago.categories.models import Category
-from misago.threads.models import Thread
-from misago.threads.threadtypes import trees_map
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -15,8 +12,6 @@ class StartThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
     def setUp(self):
         super(StartThreadTests, self).setUp()
         super(StartThreadTests, self).setUp()
 
 
-        threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
-
         self.category = Category.objects.get(slug='first-category')
         self.category = Category.objects.get(slug='first-category')
         self.api_link = reverse('misago:api:thread-list')
         self.api_link = reverse('misago:api:thread-list')
 
 

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

@@ -91,5 +91,4 @@ class ThreadParticipantTests(TestCase):
         self.assertEqual(self.thread.participants.count(), 1)
         self.assertEqual(self.thread.participants.count(), 1)
 
 
         with self.assertRaises(ThreadParticipant.DoesNotExist):
         with self.assertRaises(ThreadParticipant.DoesNotExist):
-            participant = ThreadParticipant.objects.get(
-                thread=self.thread, user=user)
+            ThreadParticipant.objects.get(thread=self.thread, user=user)

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

@@ -625,7 +625,7 @@ class EditReplyEditorApiTests(EditorApiTestCase):
 
 
     def test_edit(self):
     def test_edit(self):
         """endpoint returns valid configuration for editor"""
         """endpoint returns valid configuration for editor"""
-        for i in range(3):
+        for _ in range(3):
             self.override_acl({
             self.override_acl({
                 'max_attachment_size': 1000,
                 'max_attachment_size': 1000,
             })
             })

+ 8 - 7
misago/threads/tests/test_threads_merge_api.py

@@ -4,7 +4,6 @@ from django.urls import reverse
 from django.utils.six.moves import range
 from django.utils.six.moves import range
 
 
 from misago.acl import add_acl
 from misago.acl import add_acl
-from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.threads.api.threadendpoints.merge import MERGE_LIMIT
 from misago.threads.api.threadendpoints.merge import MERGE_LIMIT
@@ -83,7 +82,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
 
 
     def test_merge_with_nonexisting_thread(self):
     def test_merge_with_nonexisting_thread(self):
         """api validates if we are trying to merge with invalid thread"""
         """api validates if we are trying to merge with invalid thread"""
-        unaccesible_thread = testutils.post_thread(category=self.category_b)
+        testutils.post_thread(category=self.category_b)
 
 
         response = self.client.post(self.api_link, json.dumps({
         response = self.client.post(self.api_link, json.dumps({
             'threads': [self.thread.id, self.thread.id + 1000]
             'threads': [self.thread.id, self.thread.id + 1000]
@@ -139,7 +138,7 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
     def test_merge_too_many_threads(self):
     def test_merge_too_many_threads(self):
         """api rejects too many threads to merge"""
         """api rejects too many threads to merge"""
         threads = []
         threads = []
-        for i in range(MERGE_LIMIT + 1):
+        for _ in range(MERGE_LIMIT + 1):
             threads.append(testutils.post_thread(category=self.category).pk)
             threads.append(testutils.post_thread(category=self.category).pk)
 
 
         self.override_acl({
         self.override_acl({
@@ -699,8 +698,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         })
         })
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+
+        testutils.post_poll(self.thread, self.user)
+        testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(self.api_link, json.dumps({
         response = self.client.post(self.api_link, json.dumps({
             'threads': [self.thread.id, other_thread.id],
             'threads': [self.thread.id, other_thread.id],
@@ -725,8 +725,9 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
         })
         })
 
 
         other_thread = testutils.post_thread(self.category)
         other_thread = testutils.post_thread(self.category)
-        poll = testutils.post_poll(self.thread, self.user)
-        other_poll = testutils.post_poll(other_thread, self.user)
+
+        testutils.post_poll(self.thread, self.user)
+        testutils.post_poll(other_thread, self.user)
 
 
         response = self.client.post(self.api_link, json.dumps({
         response = self.client.post(self.api_link, json.dumps({
             'threads': [self.thread.id, other_thread.id],
             'threads': [self.thread.id, other_thread.id],

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

@@ -1,6 +1,6 @@
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.threads import moderation, testutils
 from misago.threads import moderation, testutils
-from misago.threads.models import Post, Thread
+from misago.threads.models import Thread
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 

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

@@ -9,9 +9,7 @@ from django.utils.six.moves import range
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
 from misago.conf import settings
 from misago.conf import settings
-from misago.core import threadstore
-from misago.core.cache import cache
-from misago.readtracker import categoriestracker, threadstracker
+from misago.readtracker import threadstracker
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.users.models import AnonymousUser
 from misago.users.models import AnonymousUser
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -374,7 +372,7 @@ class AllThreadsListTests(ThreadsListTestCase):
     def test_noscript_pagination(self):
     def test_noscript_pagination(self):
         """threads list is paginated for users with js disabled"""
         """threads list is paginated for users with js disabled"""
         threads = []
         threads = []
-        for i in range(settings.MISAGO_THREADS_PER_PAGE * 3):
+        for _ in range(settings.MISAGO_THREADS_PER_PAGE * 3):
             threads.append(testutils.post_thread(
             threads.append(testutils.post_thread(
                 category=self.first_category
                 category=self.first_category
             ))
             ))
@@ -592,8 +590,8 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(self.root, position='last-child', save=True)
         ).insert_at(self.root, position='last-child', save=True)
-        test_category = Category.objects.get(slug='hidden-category')
 
 
+        test_category = Category.objects.get(slug='hidden-category')
         test_thread = testutils.post_thread(
         test_thread = testutils.post_thread(
             category=test_category
             category=test_category
         )
         )
@@ -601,6 +599,7 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
         response = self.client.get('/')
         response = self.client.get('/')
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
         self.assertContains(response, "empty-message")
         self.assertContains(response, "empty-message")
+        self.assertNotContains(response, test_thread.get_absolute_url())
 
 
     def test_api_hides_hidden_thread(self):
     def test_api_hides_hidden_thread(self):
         """api returns empty due to no permission to see thread"""
         """api returns empty due to no permission to see thread"""
@@ -608,9 +607,10 @@ class ThreadsVisibilityTests(ThreadsListTestCase):
             name='Hidden Category',
             name='Hidden Category',
             slug='hidden-category',
             slug='hidden-category',
         ).insert_at(self.root, position='last-child', save=True)
         ).insert_at(self.root, position='last-child', save=True)
+
         test_category = Category.objects.get(slug='hidden-category')
         test_category = Category.objects.get(slug='hidden-category')
 
 
-        test_thread = testutils.post_thread(
+        testutils.post_thread(
             category=test_category,
             category=test_category,
         )
         )
 
 

+ 5 - 6
misago/threads/tests/test_threadview.py

@@ -3,7 +3,6 @@ from misago.categories.models import Category
 from misago.conf import settings
 from misago.conf import settings
 from misago.threads import testutils
 from misago.threads import testutils
 from misago.threads.events import record_event
 from misago.threads.events import record_event
-from misago.threads.models import Post, Thread
 from misago.threads.moderation import threads as threads_moderation
 from misago.threads.moderation import threads as threads_moderation
 from misago.threads.moderation import hide_post
 from misago.threads.moderation import hide_post
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -317,7 +316,7 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
         events_limit = settings.MISAGO_EVENTS_PER_PAGE
         events_limit = settings.MISAGO_EVENTS_PER_PAGE
         events = []
         events = []
 
 
-        for i in range(events_limit + 5):
+        for _ in range(events_limit + 5):
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             events.append(event)
             events.append(event)
 
 
@@ -335,12 +334,12 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
         events_limit = settings.MISAGO_EVENTS_PER_PAGE
         events_limit = settings.MISAGO_EVENTS_PER_PAGE
         events = []
         events = []
 
 
-        for i in range(events_limit + 5):
+        for _ in range(events_limit + 5):
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             events.append(event)
             events.append(event)
 
 
         posts = []
         posts = []
-        for i in range(posts_limit - 1):
+        for _ in range(posts_limit - 1):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
             posts.append(post)
             posts.append(post)
 
 
@@ -353,9 +352,9 @@ class ThreadEventVisibilityTests(ThreadViewTestCase):
             self.assertContains(response, post.get_absolute_url())
             self.assertContains(response, post.get_absolute_url())
 
 
         # add second page to thread with more events
         # add second page to thread with more events
-        for i in range(posts_limit):
+        for _ in range(posts_limit):
             post = testutils.reply_thread(self.thread)
             post = testutils.reply_thread(self.thread)
-        for i in range(events_limit):
+        for _ in range(events_limit):
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             event = record_event(MockRequest(self.user), self.thread, 'closed')
             events.append(event)
             events.append(event)
 
 

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

@@ -10,7 +10,7 @@ class ValidatePostTests(TestCase):
         """valid post passes validation"""
         """valid post passes validation"""
         validate_post("Lorem ipsum dolor met sit amet elit.")
         validate_post("Lorem ipsum dolor met sit amet elit.")
 
 
-    def test_too_short_post(self):
+    def test_empty_post(self):
         """empty post is rejected"""
         """empty post is rejected"""
         with self.assertRaises(ValidationError):
         with self.assertRaises(ValidationError):
             validate_post("")
             validate_post("")

+ 8 - 8
misago/threads/urls/__init__.py

@@ -6,8 +6,8 @@ from misago.threads.views.attachment import attachment_server
 from misago.threads.views.goto import (
 from misago.threads.views.goto import (
     ThreadGotoPostView, ThreadGotoLastView, ThreadGotoNewView, ThreadGotoUnapprovedView,
     ThreadGotoPostView, ThreadGotoLastView, ThreadGotoNewView, ThreadGotoUnapprovedView,
     PrivateThreadGotoPostView, PrivateThreadGotoLastView, PrivateThreadGotoNewView)
     PrivateThreadGotoPostView, PrivateThreadGotoLastView, PrivateThreadGotoNewView)
-from misago.threads.views.list import ForumThreads, CategoryThreads, PrivateThreads
-from misago.threads.views.thread import Thread, PrivateThread
+from misago.threads.views.list import ForumThreadsList, CategoryThreadsList, PrivateThreadsList
+from misago.threads.views.thread import ThreadView, PrivateThreadView
 
 
 
 
 LISTS_TYPES = (
 LISTS_TYPES = (
@@ -38,7 +38,7 @@ def threads_list_patterns(prefix, view, patterns):
 
 
 
 
 if settings.MISAGO_THREADS_ON_INDEX:
 if settings.MISAGO_THREADS_ON_INDEX:
-    urlpatterns = threads_list_patterns('threads', ForumThreads, (
+    urlpatterns = threads_list_patterns('threads', ForumThreadsList, (
         r'^$',
         r'^$',
         r'^my/$',
         r'^my/$',
         r'^new/$',
         r'^new/$',
@@ -47,7 +47,7 @@ if settings.MISAGO_THREADS_ON_INDEX:
         r'^unapproved/$',
         r'^unapproved/$',
     ))
     ))
 else:
 else:
-    urlpatterns = threads_list_patterns('threads', ForumThreads, (
+    urlpatterns = threads_list_patterns('threads', ForumThreadsList, (
         r'^threads/$',
         r'^threads/$',
         r'^threads/my/$',
         r'^threads/my/$',
         r'^threads/new/$',
         r'^threads/new/$',
@@ -57,7 +57,7 @@ else:
     ))
     ))
 
 
 
 
-urlpatterns += threads_list_patterns('category', CategoryThreads, (
+urlpatterns += threads_list_patterns('category', CategoryThreadsList, (
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/$',
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/$',
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/my/$',
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/my/$',
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/new/$',
     r'^c/(?P<slug>[-a-zA-Z0-9]+)/(?P<pk>\d+)/new/$',
@@ -67,7 +67,7 @@ urlpatterns += threads_list_patterns('category', CategoryThreads, (
 ))
 ))
 
 
 
 
-urlpatterns += threads_list_patterns('private-threads', PrivateThreads, (
+urlpatterns += threads_list_patterns('private-threads', PrivateThreadsList, (
     r'^private-threads/$',
     r'^private-threads/$',
     r'^private-threads/my/$',
     r'^private-threads/my/$',
     r'^private-threads/new/$',
     r'^private-threads/new/$',
@@ -84,8 +84,8 @@ def thread_view_patterns(prefix, view):
     return urls
     return urls
 
 
 
 
-urlpatterns += thread_view_patterns('thread', Thread)
-urlpatterns += thread_view_patterns('private-thread', PrivateThread)
+urlpatterns += thread_view_patterns('thread', ThreadView)
+urlpatterns += thread_view_patterns('private-thread', PrivateThreadView)
 
 
 
 
 def goto_patterns(prefix, **views):
 def goto_patterns(prefix, **views):

+ 0 - 3
misago/threads/views/admin/attachments.py

@@ -1,8 +1,5 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.db import transaction
 from django.db import transaction
-from django.db.models import Count
-from django.shortcuts import redirect
-from django.urls import reverse
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 
 from misago.admin.views import generic
 from misago.admin.views import generic

+ 0 - 2
misago/threads/views/admin/attachmenttypes.py

@@ -1,7 +1,5 @@
 from django.contrib import messages
 from django.contrib import messages
 from django.db.models import Count
 from django.db.models import Count
-from django.shortcuts import redirect
-from django.urls import reverse
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 
 from misago.admin.views import generic
 from misago.admin.views import generic

+ 0 - 3
misago/threads/views/attachment.py

@@ -1,9 +1,6 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-import os
-
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
-from django.db.models import F
 from django.http import Http404
 from django.http import Http404
 from django.shortcuts import get_object_or_404, redirect
 from django.shortcuts import get_object_or_404, redirect
 
 

+ 5 - 6
misago/threads/views/list.py

@@ -1,4 +1,3 @@
-from django.conf import settings
 from django.http import Http404
 from django.http import Http404
 from django.shortcuts import render
 from django.shortcuts import render
 from django.urls import reverse
 from django.urls import reverse
@@ -9,7 +8,7 @@ from misago.threads.viewmodels import (
     ForumThreads, PrivateThreads, PrivateThreadsCategory, ThreadsCategory, ThreadsRootCategory)
     ForumThreads, PrivateThreads, PrivateThreadsCategory, ThreadsCategory, ThreadsRootCategory)
 
 
 
 
-class ListBase(View):
+class ThreadsList(View):
     category = None
     category = None
     threads = None
     threads = None
 
 
@@ -56,7 +55,7 @@ class ListBase(View):
         return {}
         return {}
 
 
 
 
-class ForumThreads(ListBase):
+class ForumThreadsList(ThreadsList):
     category = ThreadsRootCategory
     category = ThreadsRootCategory
     threads = ForumThreads
     threads = ForumThreads
 
 
@@ -68,19 +67,19 @@ class ForumThreads(ListBase):
         }
         }
 
 
 
 
-class CategoryThreads(ForumThreads):
+class CategoryThreadsList(ForumThreadsList):
     category = ThreadsCategory
     category = ThreadsCategory
 
 
     template_name = 'misago/threadslist/category.html'
     template_name = 'misago/threadslist/category.html'
 
 
     def get_category(self, request, **kwargs):
     def get_category(self, request, **kwargs):
-        category = super(CategoryThreads, self).get_category(request, **kwargs)
+        category = super(CategoryThreadsList, self).get_category(request, **kwargs)
         if not category.level:
         if not category.level:
             raise Http404() # disallow root category access
             raise Http404() # disallow root category access
         return category
         return category
 
 
 
 
-class PrivateThreads(ListBase):
+class PrivateThreadsList(ThreadsList):
     category = PrivateThreadsCategory
     category = PrivateThreadsCategory
     threads = PrivateThreads
     threads = PrivateThreads
 
 

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

@@ -56,7 +56,7 @@ class ThreadBase(View):
         return context
         return context
 
 
 
 
-class Thread(ThreadBase):
+class ThreadView(ThreadBase):
     thread = ForumThread
     thread = ForumThread
     template_name = 'misago/thread/thread.html'
     template_name = 'misago/thread/thread.html'
 
 
@@ -66,6 +66,6 @@ class Thread(ThreadBase):
         }
         }
 
 
 
 
-class PrivateThread(ThreadBase):
+class PrivateThreadView(ThreadBase):
     thread = PrivateThread
     thread = PrivateThread
     template_name = 'misago/thread/private_thread.html'
     template_name = 'misago/thread/private_thread.html'

+ 1 - 1
misago/users/api/userendpoints/avatar.py

@@ -3,7 +3,7 @@ import json
 from rest_framework import status
 from rest_framework import status
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
-from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
 from misago.conf import settings
 from misago.conf import settings

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

@@ -10,7 +10,6 @@ from misago.conf import settings
 from misago.core.mail import mail_user
 from misago.core.mail import mail_user
 from misago.users import captcha
 from misago.users import captcha
 from misago.users.forms.register import RegisterForm
 from misago.users.forms.register import RegisterForm
-from misago.users.serializers import AuthenticatedUserSerializer
 from misago.users.tokens import make_activation_token
 from misago.users.tokens import make_activation_token
 
 
 
 

+ 1 - 9
misago/users/api/userendpoints/list.py

@@ -1,18 +1,10 @@
-from datetime import timedelta
-
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-from django.db.models import Count
-from django.http import Http404
 from django.shortcuts import get_object_or_404
 from django.shortcuts import get_object_or_404
-from django.utils import timezone
 
 
-from misago.conf import settings
-from misago.core.cache import cache
-from misago.core.shortcuts import get_int_or_404, paginate
+from misago.core.shortcuts import get_int_or_404
 from misago.users.models import Rank
 from misago.users.models import Rank
-from misago.users.online.utils import make_users_status_aware
 from misago.users.serializers import UserCardSerializer
 from misago.users.serializers import UserCardSerializer
 from misago.users.viewmodels import ActivePosters, RankUsers
 from misago.users.viewmodels import ActivePosters, RankUsers
 
 

+ 0 - 2
misago/users/api/userendpoints/username.py

@@ -59,7 +59,6 @@ def change_username(request):
         except IntegrityError:
         except IntegrityError:
             return Response({
             return Response({
                 'detail': _("Error changing username. Please try again."),
                 'detail': _("Error changing username. Please try again."),
-                'options': options
             },
             },
             status=status.HTTP_400_BAD_REQUEST)
             status=status.HTTP_400_BAD_REQUEST)
     else:
     else:
@@ -87,7 +86,6 @@ def moderate_username_endpoint(request, profile):
             except IntegrityError:
             except IntegrityError:
                 return Response({
                 return Response({
                     'detail': _("Error changing username. Please try again."),
                     'detail': _("Error changing username. Please try again."),
-                    'options': options
                 }, status=status.HTTP_400_BAD_REQUEST)
                 }, status=status.HTTP_400_BAD_REQUEST)
         else:
         else:
             return Response({
             return Response({

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

@@ -1,4 +1,4 @@
-from rest_framework import mixins, status, viewsets
+from rest_framework import viewsets
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model

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

@@ -1,9 +1,8 @@
-from rest_framework import mixins, status, viewsets
-from rest_framework.decorators import detail_route, list_route
+from rest_framework import status, viewsets
+from rest_framework.decorators import detail_route
 from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
 from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
-from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
 from django.db import transaction
 from django.db import transaction
@@ -14,7 +13,6 @@ from django.utils.translation import ugettext as _
 
 
 from misago.acl import add_acl
 from misago.acl import add_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
-from misago.core.cache import cache
 from misago.core.rest_permissions import IsAuthenticatedOrReadOnly
 from misago.core.rest_permissions import IsAuthenticatedOrReadOnly
 from misago.core.shortcuts import get_int_or_404
 from misago.core.shortcuts import get_int_or_404
 from misago.threads.moderation import hide_post, hide_thread
 from misago.threads.moderation import hide_post, hide_thread

+ 1 - 4
misago/users/apps.py

@@ -10,16 +10,13 @@ class MisagoUsersConfig(AppConfig):
     verbose_name = "Misago Auth"
     verbose_name = "Misago Auth"
 
 
     def ready(self):
     def ready(self):
-        from . import signals
+        from . import signals as _
 
 
         self.register_default_usercp_pages()
         self.register_default_usercp_pages()
         self.register_default_users_list_pages()
         self.register_default_users_list_pages()
         self.register_default_user_profile_pages()
         self.register_default_user_profile_pages()
 
 
     def register_default_usercp_pages(self):
     def register_default_usercp_pages(self):
-        def show_signature_cp(request):
-            return request.user.acl_cache['can_have_signature']
-
         usercp.add_section(
         usercp.add_section(
             link='misago:usercp-change-forum-options',
             link='misago:usercp-change-forum-options',
             name=_('Forum options'),
             name=_('Forum options'),

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

@@ -1,8 +1,7 @@
-import math
 import os
 import os
 from importlib import import_module
 from importlib import import_module
 
 
-from PIL import Image, ImageColor, ImageDraw, ImageFilter, ImageFont
+from PIL import Image, ImageColor, ImageDraw, ImageFont
 
 
 from misago.conf import settings
 from misago.conf import settings
 
 

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

@@ -1,6 +1,3 @@
-from hashlib import sha256
-from math import floor
-
 from path import Path
 from path import Path
 from PIL import Image
 from PIL import Image
 
 

+ 0 - 1
misago/users/decorators.py

@@ -1,5 +1,4 @@
 from django.core.exceptions import PermissionDenied
 from django.core.exceptions import PermissionDenied
-from django.shortcuts import redirect
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 
 
 from misago.core.exceptions import Banned
 from misago.core.exceptions import Banned

+ 0 - 1
misago/users/management/commands/buildactivepostersranking.py

@@ -1,6 +1,5 @@
 from django.core.management.base import BaseCommand
 from django.core.management.base import BaseCommand
 
 
-from misago.core.management.progressbar import show_progress
 from misago.users.activepostersranking import build_active_posters_ranking
 from misago.users.activepostersranking import build_active_posters_ranking
 
 
 
 

+ 0 - 3
misago/users/middleware.py

@@ -1,9 +1,6 @@
 from django.contrib.auth import logout
 from django.contrib.auth import logout
-from django.contrib.auth.models import AnonymousUser as DjAnonymousUser
 from django.utils.deprecation import MiddlewareMixin
 from django.utils.deprecation import MiddlewareMixin
 
 
-import pytz
-
 from .bans import get_request_ip_ban, get_user_ban
 from .bans import get_request_ip_ban, get_user_ban
 from .models import AnonymousUser, Online
 from .models import AnonymousUser, Online
 from .online import tracker
 from .online import tracker

+ 1 - 1
misago/users/migrations/0003_bans_version_tracker.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 
 
 from misago.core.migrationutils import cachebuster_register_cache
 from misago.core.migrationutils import cachebuster_register_cache
 from misago.users.constants import BANS_CACHEBUSTER
 from misago.users.constants import BANS_CACHEBUSTER

+ 1 - 1
misago/users/migrations/0004_default_ranks.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
-from django.db import migrations, models
+from django.db import migrations
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
 from misago.core.utils import slugify
 from misago.core.utils import slugify

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

@@ -7,7 +7,6 @@ from django.contrib.auth.password_validation import validate_password
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.fields import JSONField
 from django.core.mail import send_mail
 from django.core.mail import send_mail
 from django.db import IntegrityError, models, transaction
 from django.db import IntegrityError, models, transaction
-from django.dispatch import receiver
 from django.urls import reverse
 from django.urls import reverse
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
@@ -17,7 +16,7 @@ from misago.acl.models import Role
 from misago.conf import settings
 from misago.conf import settings
 from misago.core.utils import slugify
 from misago.core.utils import slugify
 from misago.users import avatars
 from misago.users import avatars
-from misago.users.signatures import is_user_signature_valid, make_signature_checksum
+from misago.users.signatures import is_user_signature_valid
 from misago.users.utils import hash_email
 from misago.users.utils import hash_email
 
 
 from .rank import Rank
 from .rank import Rank

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

@@ -36,7 +36,7 @@ class ModerateSignatureSerializer(serializers.ModelSerializer):
     def validate_signature(self, value):
     def validate_signature(self, value):
         length_limit = settings.signature_length_max
         length_limit = settings.signature_length_max
         if len(value) > length_limit:
         if len(value) > length_limit:
-            raise forms.ValidationError(ungettext(
+            raise serializers.ValidationError(ungettext(
                 "Signature can't be longer than %(limit)s character.",
                 "Signature can't be longer than %(limit)s character.",
                 "Signature can't be longer than %(limit)s characters.",
                 "Signature can't be longer than %(limit)s characters.",
                 length_limit) % {'limit': length_limit})
                 length_limit) % {'limit': length_limit})

+ 0 - 3
misago/users/templatetags/misago_avatars.py

@@ -1,7 +1,4 @@
 from django import template
 from django import template
-from django.urls import reverse
-
-from misago.conf import settings
 
 
 
 
 register = template.Library()
 register = template.Library()

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

@@ -83,7 +83,7 @@ class GatewayTests(TestCase):
         user.is_staff = True
         user.is_staff = True
         user.save()
         user.save()
 
 
-        ban = Ban.objects.create(
+        Ban.objects.create(
             check_type=Ban.USERNAME,
             check_type=Ban.USERNAME,
             banned_value='bob',
             banned_value='bob',
             user_message='You are tragically banned.',
             user_message='You are tragically banned.',

+ 0 - 1
misago/users/tests/test_auth_views.py

@@ -1,6 +1,5 @@
 import json
 import json
 
 
-from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.test import TestCase
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.encoding import smart_str
 from django.utils.encoding import smart_str

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

@@ -3,7 +3,7 @@ from PIL import Image
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
-from django.test import TestCase, override_settings
+from django.test import TestCase
 from django.utils.crypto import get_random_string
 from django.utils.crypto import get_random_string
 
 
 from misago.conf import settings
 from misago.conf import settings
@@ -24,7 +24,7 @@ class AvatarsStoreTests(TestCase):
         store.store_new_avatar(user, test_image)
         store.store_new_avatar(user, test_image)
 
 
         # reload user
         # reload user
-        test_user = UserModel.objects.get(pk=user.pk)
+        UserModel.objects.get(pk=user.pk)
 
 
         # assert that avatars were stored in media
         # assert that avatars were stored in media
         avatars_dict = {}
         avatars_dict = {}

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

@@ -1,4 +1,4 @@
-from datetime import date, datetime, timedelta
+from datetime import datetime, timedelta
 
 
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.six.moves import range
 from django.utils.six.moves import range

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

@@ -62,8 +62,6 @@ class ForgottenPasswordViewsTests(UserTestCase):
         """invalid form token errors"""
         """invalid form token errors"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'Pass.123')
 
 
-        password_token = make_password_change_token(test_user)
-
         response = self.client.get(
         response = self.client.get(
             reverse('misago:forgotten-password-change-form', kwargs={
             reverse('misago:forgotten-password-change-form', kwargs={
                 'pk': test_user.pk,
                 'pk': test_user.pk,

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

@@ -19,7 +19,7 @@ class InvalidateBansTests(TestCase):
     def test_expired_bans_handling(self):
     def test_expired_bans_handling(self):
         """expired bans are flagged as such"""
         """expired bans are flagged as such"""
         # create 5 bans then update their valid date to past one
         # create 5 bans then update their valid date to past one
-        for i in range(5):
+        for _ in range(5):
             Ban.objects.create(banned_value="abcd")
             Ban.objects.create(banned_value="abcd")
         expired_date = (timezone.now() - timedelta(days=10))
         expired_date = (timezone.now() - timedelta(days=10))
         Ban.objects.all().update(expires_on=expired_date, is_checked=True)
         Ban.objects.all().update(expires_on=expired_date, is_checked=True)

+ 0 - 4
misago/users/tests/test_user_avatar_api.py

@@ -4,11 +4,8 @@ import os
 from path import Path
 from path import Path
 
 
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
-from django.urls import reverse
-from django.utils.encoding import smart_str
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
-from misago.conf import settings
 from misago.users.avatars import gallery, store
 from misago.users.avatars import gallery, store
 from misago.users.models import AvatarGallery
 from misago.users.models import AvatarGallery
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -184,7 +181,6 @@ class UserAvatarTests(AuthenticatedUserTestCase):
         self.assertContains(response, "Avatar was re-cropped.")
         self.assertContains(response, "Avatar was re-cropped.")
 
 
         # delete user avatars, test if it deletes src and tmp
         # delete user avatars, test if it deletes src and tmp
-        user = self.get_current_user()
         store.delete_avatar(self.get_current_user())
         store.delete_avatar(self.get_current_user())
 
 
         self.assertTrue(self.get_current_user().avatar_src.path)
         self.assertTrue(self.get_current_user().avatar_src.path)

+ 0 - 1
misago/users/tests/test_user_changepassword_api.py

@@ -1,4 +1,3 @@
-from django.contrib.auth import get_user_model
 from django.core import mail
 from django.core import mail
 from django.urls import reverse
 from django.urls import reverse
 
 

+ 2 - 2
misago/users/tests/test_user_feeds_api.py

@@ -102,7 +102,7 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
 
     def test_user_hidden_post(self):
     def test_user_hidden_post(self):
         """hidden posts don't show in feeds at all"""
         """hidden posts don't show in feeds at all"""
-        post = testutils.reply_thread(self.thread, poster=self.user, is_hidden=True)
+        testutils.reply_thread(self.thread, poster=self.user, is_hidden=True)
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -110,7 +110,7 @@ class UserPostsApiTests(ThreadsApiTestCase):
 
 
     def test_user_unapproved_post(self):
     def test_user_unapproved_post(self):
         """unapproved posts don't show in feeds at all"""
         """unapproved posts don't show in feeds at all"""
-        post = testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
+        testutils.reply_thread(self.thread, poster=self.user, is_unapproved=True)
 
 
         response = self.client.get(self.api_link)
         response = self.client.get(self.api_link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

+ 4 - 13
misago/users/tests/test_user_signature_api.py

@@ -1,10 +1,4 @@
-import json
-
-from django.contrib.auth import get_user_model
-from django.utils.encoding import smart_str
-
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
-from misago.conf import settings
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 
 
 
@@ -50,8 +44,7 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        response_json = json.loads(smart_str(response.content))
-        self.assertFalse(response_json['signature'])
+        self.assertFalse(response.json()['signature'])
 
 
     def test_post_empty_signature(self):
     def test_post_empty_signature(self):
         """empty POST empties user signature"""
         """empty POST empties user signature"""
@@ -65,8 +58,7 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         response = self.client.post(self.link, data={'signature': ''})
         response = self.client.post(self.link, data={'signature': ''})
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        response_json = json.loads(smart_str(response.content))
-        self.assertFalse(response_json['signature'])
+        self.assertFalse(response.json()['signature'])
 
 
     def test_post_too_long_signature(self):
     def test_post_too_long_signature(self):
         """too long new signature errors"""
         """too long new signature errors"""
@@ -96,10 +88,9 @@ class UserSignatureTests(AuthenticatedUserTestCase):
         })
         })
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
 
 
-        response_json = json.loads(smart_str(response.content))
-        self.assertEqual(response_json['signature']['html'],
+        self.assertEqual(response.json()['signature']['html'],
                          '<p>Hello, <strong>bros</strong>!</p>')
                          '<p>Hello, <strong>bros</strong>!</p>')
-        self.assertEqual(response_json['signature']['plain'],
+        self.assertEqual(response.json()['signature']['plain'],
                          'Hello, **bros**!')
                          'Hello, **bros**!')
 
 
         self.reload_user()
         self.reload_user()

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

@@ -83,7 +83,7 @@ class UserUsernameTests(AuthenticatedUserTestCase):
         response = self.client.get(self.link)
         response = self.client.get(self.link)
         changes_left = json.loads(smart_str(response.content))['changes_left']
         changes_left = json.loads(smart_str(response.content))['changes_left']
 
 
-        username = self.user.username
+        old_username = self.user.username
         new_username = 'NewUsernamu'
         new_username = 'NewUsernamu'
 
 
         response = self.client.post(self.link, data={
         response = self.client.post(self.link, data={
@@ -96,6 +96,7 @@ class UserUsernameTests(AuthenticatedUserTestCase):
 
 
         self.reload_user()
         self.reload_user()
         self.assertEqual(self.user.username, new_username)
         self.assertEqual(self.user.username, new_username)
+        self.assertTrue(self.user.username != old_username)
 
 
         self.assertEqual(self.user.namechanges.last().new_username,
         self.assertEqual(self.user.namechanges.last().new_username,
                          new_username)
                          new_username)

+ 2 - 44
misago/users/tests/test_useradmin_views.py

@@ -484,48 +484,6 @@ class UserAdminViewsTests(AdminTestCase):
         self.assertTrue(updated_user.is_active)
         self.assertTrue(updated_user.is_active)
         self.assertFalse(updated_user.is_active_staff_message)
         self.assertFalse(updated_user.is_active_staff_message)
 
 
-    def test_edit_superuser_disable_admin(self):
-        """edit user view allows superuser to disable admin"""
-        self.user.is_superuser = True
-        self.user.save()
-
-        test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
-
-        test_user.is_staff = True
-        test_user.save()
-
-        test_link = reverse('misago:admin:users:accounts:edit',
-                            kwargs={'pk': test_user.pk})
-
-        response = self.client.get(test_link)
-        self.assertContains(response, 'id="id_is_active_1"')
-        self.assertContains(response, 'id="id_is_active_staff_message"')
-
-        response = self.client.post(test_link, data={
-            'username': 'Bawww',
-            'rank': six.text_type(test_user.rank_id),
-            'roles': six.text_type(test_user.roles.all()[0].pk),
-            'email': 'reg@stered.com',
-            'new_password': 'pass123',
-            'is_staff': '1',
-            'is_superuser': '0',
-            'signature': 'Hello world!',
-            'is_signature_locked': '1',
-            'is_hiding_presence': '0',
-            'limits_private_thread_invites_to': '0',
-            'signature_lock_staff_message': 'Staff message',
-            'signature_lock_user_message': 'User message',
-            'subscribe_to_started_threads': '2',
-            'subscribe_to_replied_threads': '2',
-            'is_active': '0',
-            'is_active_staff_message': "Disabled in test!"
-        })
-        self.assertEqual(response.status_code, 302)
-
-        updated_user = UserModel.objects.get(pk=test_user.pk)
-        self.assertFalse(updated_user.is_active)
-        self.assertEqual(updated_user.is_active_staff_message, "Disabled in test!")
-
     def test_delete_threads_view(self):
     def test_delete_threads_view(self):
         """delete user threads view deletes threads"""
         """delete user threads view deletes threads"""
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
         test_user = UserModel.objects.create_user('Bob', 'bob@test.com', 'pass123')
@@ -533,7 +491,7 @@ class UserAdminViewsTests(AdminTestCase):
                             kwargs={'pk': test_user.pk})
                             kwargs={'pk': test_user.pk})
 
 
         category = Category.objects.all_categories()[:1][0]
         category = Category.objects.all_categories()[:1][0]
-        [post_thread(category, poster=test_user) for i in range(10)]
+        [post_thread(category, poster=test_user) for _ in range(10)]
 
 
         response = self.client.post(test_link, **self.AJAX_HEADER)
         response = self.client.post(test_link, **self.AJAX_HEADER)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)
@@ -557,7 +515,7 @@ class UserAdminViewsTests(AdminTestCase):
 
 
         category = Category.objects.all_categories()[:1][0]
         category = Category.objects.all_categories()[:1][0]
         thread = post_thread(category)
         thread = post_thread(category)
-        [reply_thread(thread, poster=test_user) for i in range(10)]
+        [reply_thread(thread, poster=test_user) for _ in range(10)]
 
 
         response = self.client.post(test_link, **self.AJAX_HEADER)
         response = self.client.post(test_link, **self.AJAX_HEADER)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(response.status_code, 200)

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

@@ -1,5 +1,3 @@
-from django.contrib.auth import get_user_model
-
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.users.testutils import AuthenticatedUserTestCase
 from misago.users.testutils import AuthenticatedUserTestCase
 
 

+ 0 - 15
misago/users/tests/test_users_api.py

@@ -7,7 +7,6 @@ from django.utils.encoding import smart_str
 
 
 from misago.acl.testutils import override_acl
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.categories.models import Category
-from misago.conf import settings
 from misago.core import threadstore
 from misago.core import threadstore
 from misago.core.cache import cache
 from misago.core.cache import cache
 from misago.threads.models import Post, Thread
 from misago.threads.models import Post, Thread
@@ -530,20 +529,6 @@ class UserDeleteTests(AuthenticatedUserTestCase):
         response = self.client.post(self.link)
         response = self.client.post(self.link)
         self.assertEqual(response.status_code, 403)
         self.assertEqual(response.status_code, 403)
         self.assertContains(response, "can't delete users", status_code=403)
         self.assertContains(response, "can't delete users", status_code=403)
-
-    def test_delete_too_many_posts(self):
-        """raises 403 error when user has too many posts"""
-        override_acl(self.user, {
-            'can_delete_users_newer_than': 0,
-            'can_delete_users_with_less_posts_than': 5,
-        })
-
-        self.other_user.posts = 6
-        self.other_user.save()
-
-        response = self.client.post(self.link)
-        self.assertEqual(response.status_code, 403)
-        self.assertContains(response, "can't delete users", status_code=403)
         self.assertContains(response, "made more than 5 posts", status_code=403)
         self.assertContains(response, "made more than 5 posts", status_code=403)
 
 
     def test_delete_too_old_member(self):
     def test_delete_too_old_member(self):

+ 2 - 2
misago/users/validators.py

@@ -149,9 +149,9 @@ def validate_gmail_email(request, form, cleaned_data):
 """
 """
 Registration validation
 Registration validation
 """
 """
-def load_registration_validators(validators_list):
+def load_registration_validators(validators):
     loaded_validators = []
     loaded_validators = []
-    for path in validators_list:
+    for path in validators:
         module = import_module('.'.join(path.split('.')[:-1]))
         module = import_module('.'.join(path.split('.')[:-1]))
         loaded_validators.append(getattr(module, path.split('.')[-1]))
         loaded_validators.append(getattr(module, path.split('.')[-1]))
     return loaded_validators
     return loaded_validators

+ 0 - 1
misago/users/viewmodels/rankusers.py

@@ -1,6 +1,5 @@
 from misago.conf import settings
 from misago.conf import settings
 from misago.core.shortcuts import paginate, pagination_dict
 from misago.core.shortcuts import paginate, pagination_dict
-from django.http import Http404
 from misago.users.online.utils import make_users_status_aware
 from misago.users.online.utils import make_users_status_aware
 from misago.users.serializers import UserCardSerializer
 from misago.users.serializers import UserCardSerializer
 
 

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

@@ -3,9 +3,7 @@ from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
 from django.urls import reverse
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
-from misago.conf import settings
 from misago.core.exceptions import Banned
 from misago.core.exceptions import Banned
-from misago.core.mail import mail_user
 from misago.users.bans import get_user_ban
 from misago.users.bans import get_user_ban
 from misago.users.decorators import deny_authenticated, deny_banned_ips
 from misago.users.decorators import deny_authenticated, deny_banned_ips
 from misago.users.tokens import is_activation_token_valid
 from misago.users.tokens import is_activation_token_valid

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

@@ -15,7 +15,7 @@ from misago.threads.models import Thread
 from misago.users.avatars.dynamic import set_avatar as set_dynamic_avatar
 from misago.users.avatars.dynamic import set_avatar as set_dynamic_avatar
 from misago.users.forms.admin import (
 from misago.users.forms.admin import (
     BanUsersForm, EditUserForm, EditUserFormFactory, NewUserForm, SearchUsersForm)
     BanUsersForm, EditUserForm, EditUserFormFactory, NewUserForm, SearchUsersForm)
-from misago.users.models import Ban, User
+from misago.users.models import Ban
 from misago.users.signatures import set_user_signature
 from misago.users.signatures import set_user_signature
 
 
 
 
@@ -212,11 +212,6 @@ class UsersList(UserAdmin, generic.ListView):
         messages.success(request, message)
         messages.success(request, message)
 
 
     def action_delete_all(self, request, users):
     def action_delete_all(self, request, users):
-        return self.render(
-            request, template='misago/admin/users/delete.html', context={
-                'users': users,
-            })
-
         for user in users:
         for user in users:
             if user.is_staff or user.is_superuser:
             if user.is_staff or user.is_superuser:
                 message = _("%(user)s is admin and can't be deleted.")
                 message = _("%(user)s is admin and can't be deleted.")
@@ -229,6 +224,14 @@ class UsersList(UserAdmin, generic.ListView):
         message = _("Selected users and their content has been deleted.")
         message = _("Selected users and their content has been deleted.")
         messages.success(request, message)
         messages.success(request, message)
 
 
+        return self.render(
+            request,
+            template='misago/admin/users/delete.html',
+            context={
+                'users': users,
+            }
+        )
+
 
 
 class NewUser(UserAdmin, generic.ModelFormView):
 class NewUser(UserAdmin, generic.ModelFormView):
     form = NewUserForm
     form = NewUserForm

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

@@ -1,7 +1,6 @@
 from django.contrib.auth import update_session_auth_hash
 from django.contrib.auth import update_session_auth_hash
 from django.db import IntegrityError
 from django.db import IntegrityError
 from django.shortcuts import render
 from django.shortcuts import render
-from django.urls import reverse
 from django.utils import six
 from django.utils import six
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
@@ -36,7 +35,7 @@ def confirm_change_view(f):
     def decorator(request, token):
     def decorator(request, token):
         try:
         try:
             return f(request, token)
             return f(request, token)
-        except ChangeError as e:
+        except ChangeError:
             return render(request, 'misago/options/credentials_error.html',
             return render(request, 'misago/options/credentials_error.html',
                 status=400)
                 status=400)
     return decorator
     return decorator