Browse Source

Merge pull request #1122 from rafalp/py36-only

Make Misago Python3.6 only
Rafał Pitoń 6 years ago
parent
commit
4b973e4058
264 changed files with 444 additions and 725 deletions
  1. 0 3
      .travis.yml
  2. 1 1
      Dockerfile
  3. 2 2
      README.rst
  4. 0 3
      misago/acl/migrations/0001_initial.py
  5. 0 3
      misago/acl/migrations/0002_acl_version_tracker.py
  6. 0 3
      misago/acl/migrations/0003_default_roles.py
  7. 2 4
      misago/acl/models.py
  8. 2 3
      misago/acl/tests/test_providers.py
  9. 0 3
      misago/admin/tests/test_admin_views.py
  10. 1 1
      misago/admin/testutils.py
  11. 2 1
      misago/admin/views/generic/list.py
  12. 3 3
      misago/categories/forms.py
  13. 0 3
      misago/categories/migrations/0001_initial.py
  14. 0 3
      misago/categories/migrations/0002_default_categories.py
  15. 0 3
      misago/categories/migrations/0003_categories_roles.py
  16. 0 3
      misago/categories/migrations/0004_category_last_thread.py
  17. 0 3
      misago/categories/migrations/0005_auto_20170303_2027.py
  18. 0 3
      misago/categories/migrations/0006_moderation_queue_roles.py
  19. 0 3
      misago/categories/migrations/0007_best_answers_roles.py
  20. 2 5
      misago/categories/models.py
  21. 1 1
      misago/categories/tests/test_categories_admin_views.py
  22. 1 1
      misago/categories/tests/test_category_model.py
  23. 2 1
      misago/categories/tests/test_fixcategoriestree.py
  24. 1 1
      misago/categories/tests/test_prunecategories.py
  25. 2 1
      misago/categories/tests/test_synchronizecategories.py
  26. 1 1
      misago/categories/tests/test_utils.py
  27. 1 1
      misago/categories/tests/test_views.py
  28. 1 1
      misago/categories/views/categoriesadmin.py
  29. 3 4
      misago/conf/hydrators.py
  30. 0 3
      misago/conf/migrations/0001_initial.py
  31. 4 4
      misago/core/apipatch.py
  32. 1 2
      misago/core/exceptionhandler.py
  33. 0 3
      misago/core/migrations/0001_initial.py
  34. 0 3
      misago/core/migrations/0002_basic_settings.py
  35. 4 6
      misago/core/pgutils.py
  36. 1 3
      misago/core/shortcuts.py
  37. 1 2
      misago/core/slugify.py
  38. 1 1
      misago/core/templatetags/misago_absoluteurl.py
  39. 2 2
      misago/core/templatetags/misago_pagetitle.py
  40. 1 1
      misago/core/testproject/searchfilters.py
  41. 5 5
      misago/core/tests/test_apipatch.py
  42. 1 1
      misago/core/tests/test_cachebuster.py
  43. 1 2
      misago/core/tests/test_deprecations.py
  44. 1 1
      misago/core/tests/test_forms.py
  45. 0 4
      misago/core/tests/test_utils.py
  46. 2 2
      misago/core/testutils.py
  47. 1 2
      misago/faker/englishcorpus.py
  48. 0 2
      misago/faker/management/commands/createfakethreads.py
  49. 2 2
      misago/legal/forms.py
  50. 0 3
      misago/legal/migrations/0001_initial.py
  51. 0 3
      misago/legal/migrations/0002_agreement_useragreement.py
  52. 0 3
      misago/legal/migrations/0003_create_agreements_from_settings.py
  53. 1 1
      misago/legal/tests/test_api.py
  54. 2 2
      misago/legal/tests/test_context_processors.py
  55. 1 1
      misago/legal/tests/test_required_agreement.py
  56. 3 3
      misago/legal/views/admin.py
  57. 1 3
      misago/markup/bbcode/blocks.py
  58. 1 3
      misago/markup/checksums.py
  59. 0 2
      misago/markup/finalise.py
  60. 2 3
      misago/markup/mentions.py
  61. 7 6
      misago/markup/parser.py
  62. 1 3
      misago/markup/pipeline.py
  63. 1 4
      misago/markup/tests/test_api.py
  64. 0 3
      misago/markup/tests/test_finalise.py
  65. 0 3
      misago/markup/tests/test_parser.py
  66. 0 3
      misago/readtracker/migrations/0001_initial.py
  67. 0 3
      misago/readtracker/migrations/0002_postread.py
  68. 0 3
      misago/readtracker/migrations/0003_migrate_reads_to_posts.py
  69. 0 3
      misago/readtracker/migrations/0004_auto_20171015_2010.py
  70. 1 1
      misago/readtracker/tests/test_clearreadtracker.py
  71. 1 2
      misago/search/api.py
  72. 1 2
      misago/search/context_processors.py
  73. 3 4
      misago/search/tests/test_api.py
  74. 1 1
      misago/search/tests/test_views.py
  75. 0 1
      misago/search/views.py
  76. 0 1
      misago/threads/api/postendpoints/split.py
  77. 1 1
      misago/threads/api/postingendpoint/category.py
  78. 1 1
      misago/threads/api/postingendpoint/emailnotification.py
  79. 2 4
      misago/threads/api/postingendpoint/participants.py
  80. 1 1
      misago/threads/api/postingendpoint/savechanges.py
  81. 1 2
      misago/threads/api/threadendpoints/merge.py
  82. 2 3
      misago/threads/api/threadendpoints/patch.py
  83. 1 3
      misago/threads/checksums.py
  84. 1 1
      misago/threads/mergeconflict.py
  85. 0 3
      misago/threads/migrations/0001_initial.py
  86. 0 3
      misago/threads/migrations/0002_threads_settings.py
  87. 0 3
      misago/threads/migrations/0003_attachment_types.py
  88. 0 3
      misago/threads/migrations/0004_update_settings.py
  89. 0 3
      misago/threads/migrations/0005_index_search_document.py
  90. 0 3
      misago/threads/migrations/0006_redo_partial_indexes.py
  91. 0 3
      misago/threads/migrations/0007_auto_20171008_0131.py
  92. 0 3
      misago/threads/migrations/0008_auto_20180310_2234.py
  93. 1 3
      misago/threads/migrations/0009_auto_20180326_0010.py
  94. 0 3
      misago/threads/migrations/0010_auto_20180609_1523.py
  95. 1 3
      misago/threads/models/attachment.py
  96. 0 4
      misago/threads/models/attachmenttype.py
  97. 5 9
      misago/threads/models/post.py
  98. 1 3
      misago/threads/models/thread.py
  99. 0 4
      misago/threads/moderation/exceptions.py
  100. 1 2
      misago/threads/paginator.py
  101. 1 2
      misago/threads/serializers/moderation.py
  102. 1 1
      misago/threads/serializers/poll.py
  103. 1 1
      misago/threads/signals.py
  104. 0 2
      misago/threads/templatetags/misago_poststags.py
  105. 3 3
      misago/threads/tests/test_anonymize_data.py
  106. 1 1
      misago/threads/tests/test_attachmentadmin_views.py
  107. 7 8
      misago/threads/tests/test_attachments_api.py
  108. 1 1
      misago/threads/tests/test_attachments_middleware.py
  109. 1 1
      misago/threads/tests/test_attachmenttypeadmin_views.py
  110. 1 1
      misago/threads/tests/test_attachmentview.py
  111. 1 1
      misago/threads/tests/test_clearattachments.py
  112. 1 1
      misago/threads/tests/test_delete_user_likes.py
  113. 1 4
      misago/threads/tests/test_emailnotification_middleware.py
  114. 0 1
      misago/threads/tests/test_events.py
  115. 1 4
      misago/threads/tests/test_floodprotection.py
  116. 1 1
      misago/threads/tests/test_gotoviews.py
  117. 2 2
      misago/threads/tests/test_mergeconflict.py
  118. 1 4
      misago/threads/tests/test_post_mentions.py
  119. 1 1
      misago/threads/tests/test_posts_moderation.py
  120. 1 1
      misago/threads/tests/test_privatethread_patch_api.py
  121. 1 1
      misago/threads/tests/test_privatethread_reply_api.py
  122. 1 4
      misago/threads/tests/test_privatethread_start_api.py
  123. 1 1
      misago/threads/tests/test_privatethread_view.py
  124. 1 1
      misago/threads/tests/test_privatethreads.py
  125. 3 3
      misago/threads/tests/test_privatethreads_api.py
  126. 1 1
      misago/threads/tests/test_privatethreads_lists.py
  127. 2 2
      misago/threads/tests/test_search.py
  128. 3 6
      misago/threads/tests/test_subscription_middleware.py
  129. 1 1
      misago/threads/tests/test_sync_unread_private_threads.py
  130. 2 1
      misago/threads/tests/test_synchronizethreads.py
  131. 2 2
      misago/threads/tests/test_thread_bulkpatch_api.py
  132. 1 4
      misago/threads/tests/test_thread_editreply_api.py
  133. 3 3
      misago/threads/tests/test_thread_merge_api.py
  134. 6 6
      misago/threads/tests/test_thread_patch_api.py
  135. 1 1
      misago/threads/tests/test_thread_poll_api.py
  136. 1 1
      misago/threads/tests/test_thread_polldelete_api.py
  137. 1 1
      misago/threads/tests/test_thread_polledit_api.py
  138. 2 2
      misago/threads/tests/test_thread_pollvotes_api.py
  139. 1 1
      misago/threads/tests/test_thread_postbulkdelete_api.py
  140. 1 4
      misago/threads/tests/test_thread_postbulkpatch_api.py
  141. 2 2
      misago/threads/tests/test_thread_postdelete_api.py
  142. 2 5
      misago/threads/tests/test_thread_postedits_api.py
  143. 1 1
      misago/threads/tests/test_thread_postlikes_api.py
  144. 1 4
      misago/threads/tests/test_thread_postmerge_api.py
  145. 1 4
      misago/threads/tests/test_thread_postmove_api.py
  146. 2 5
      misago/threads/tests/test_thread_postpatch_api.py
  147. 1 1
      misago/threads/tests/test_thread_postread_api.py
  148. 1 4
      misago/threads/tests/test_thread_postsplit_api.py
  149. 1 4
      misago/threads/tests/test_thread_reply_api.py
  150. 1 4
      misago/threads/tests/test_thread_start_api.py
  151. 3 3
      misago/threads/tests/test_threads_api.py
  152. 1 1
      misago/threads/tests/test_threads_bulkdelete_api.py
  153. 4 4
      misago/threads/tests/test_threads_editor_api.py
  154. 3 3
      misago/threads/tests/test_threads_merge_api.py
  155. 2 2
      misago/threads/tests/test_threads_moderation.py
  156. 4 4
      misago/threads/tests/test_threadslists.py
  157. 8 9
      misago/threads/tests/test_threadview.py
  158. 2 3
      misago/threads/tests/test_treesmap.py
  159. 2 1
      misago/threads/tests/test_updatepostschecksums.py
  160. 1 1
      misago/threads/tests/test_utils.py
  161. 1 4
      misago/threads/tests/test_validate_post.py
  162. 3 3
      misago/threads/utils.py
  163. 1 2
      misago/threads/viewmodels/threads.py
  164. 1 1
      misago/threads/views/admin/attachments.py
  165. 2 2
      misago/threads/views/admin/attachmenttypes.py
  166. 0 2
      misago/threads/views/attachment.py
  167. 1 1
      misago/threads/views/list.py
  168. 2 2
      misago/users/api/userendpoints/editdetails.py
  169. 16 8
      misago/users/avatars/gallery.py
  170. 3 2
      misago/users/avatars/uploaded.py
  171. 2 3
      misago/users/credentialchange.py
  172. 8 10
      misago/users/datadownloads/dataarchive.py
  173. 4 4
      misago/users/forms/admin.py
  174. 2 2
      misago/users/forms/auth.py
  175. 3 3
      misago/users/forms/register.py
  176. 6 7
      misago/users/management/commands/createsuperuser.py
  177. 0 2
      misago/users/management/commands/deleteinactiveusers.py
  178. 0 2
      misago/users/management/commands/deletemarkedusers.py
  179. 0 2
      misago/users/management/commands/deleteprofilefield.py
  180. 0 2
      misago/users/management/commands/listusedprofilefields.py
  181. 0 3
      misago/users/migrations/0001_initial.py
  182. 0 3
      misago/users/migrations/0002_users_settings.py
  183. 0 3
      misago/users/migrations/0003_bans_version_tracker.py
  184. 0 3
      misago/users/migrations/0004_default_ranks.py
  185. 0 3
      misago/users/migrations/0005_dj_19_update.py
  186. 0 3
      misago/users/migrations/0006_update_settings.py
  187. 0 3
      misago/users/migrations/0007_auto_20170219_1639.py
  188. 0 3
      misago/users/migrations/0008_ban_registration_only.py
  189. 0 3
      misago/users/migrations/0009_redo_partial_indexes.py
  190. 0 3
      misago/users/migrations/0010_user_profile_fields.py
  191. 0 3
      misago/users/migrations/0011_auto_20180331_2208.py
  192. 0 3
      misago/users/migrations/0012_audittrail.py
  193. 0 3
      misago/users/migrations/0013_auto_20180609_1523.py
  194. 0 3
      misago/users/migrations/0014_datadownload.py
  195. 0 3
      misago/users/migrations/0015_user_agreements.py
  196. 2 2
      misago/users/models/ban.py
  197. 1 1
      misago/users/models/datadownload.py
  198. 2 4
      misago/users/models/rank.py
  199. 2 2
      misago/users/models/user.py
  200. 0 2
      misago/users/profilefields/__init__.py
  201. 3 4
      misago/users/profilefields/basefields.py
  202. 0 2
      misago/users/profilefields/default.py
  203. 1 1
      misago/users/social/pipeline.py
  204. 2 2
      misago/users/tests/test_activepostersranking.py
  205. 3 3
      misago/users/tests/test_audittrail.py
  206. 1 2
      misago/users/tests/test_auth_views.py
  207. 5 4
      misago/users/tests/test_avatars.py
  208. 0 1
      misago/users/tests/test_ban_model.py
  209. 5 6
      misago/users/tests/test_bio_profilefield.py
  210. 2 1
      misago/users/tests/test_createsuperuser.py
  211. 1 1
      misago/users/tests/test_datadownloads.py
  212. 13 15
      misago/users/tests/test_datadownloads_dataarchive.py
  213. 1 1
      misago/users/tests/test_deleteinactiveusers.py
  214. 2 1
      misago/users/tests/test_deletemarkedusers.py
  215. 2 1
      misago/users/tests/test_deleteprofilefield.py
  216. 1 1
      misago/users/tests/test_djangoadmin_user.py
  217. 1 1
      misago/users/tests/test_expireuserdatadownloads.py
  218. 7 8
      misago/users/tests/test_gender_profilefield.py
  219. 1 1
      misago/users/tests/test_invalidatebans.py
  220. 3 4
      misago/users/tests/test_joinip_profilefield.py
  221. 1 1
      misago/users/tests/test_lists_views.py
  222. 2 1
      misago/users/tests/test_listusedprofilefields.py
  223. 2 1
      misago/users/tests/test_loadavatargallery.py
  224. 1 1
      misago/users/tests/test_online_utils.py
  225. 2 2
      misago/users/tests/test_options_views.py
  226. 2 1
      misago/users/tests/test_populateonlinetracker.py
  227. 2 1
      misago/users/tests/test_prepareuserdatadownloads.py
  228. 1 1
      misago/users/tests/test_profile_views.py
  229. 3 4
      misago/users/tests/test_profilefields.py
  230. 1 1
      misago/users/tests/test_removeoldips.py
  231. 2 2
      misago/users/tests/test_search.py
  232. 10 11
      misago/users/tests/test_social_pipeline.py
  233. 7 8
      misago/users/tests/test_twitter_profilefield.py
  234. 6 7
      misago/users/tests/test_user_avatar_api.py
  235. 1 1
      misago/users/tests/test_user_changeemail_api.py
  236. 1 1
      misago/users/tests/test_user_changepassword_api.py
  237. 1 1
      misago/users/tests/test_user_create_api.py
  238. 1 1
      misago/users/tests/test_user_datadownloads_api.py
  239. 1 1
      misago/users/tests/test_user_editdetails_api.py
  240. 2 2
      misago/users/tests/test_user_feeds_api.py
  241. 1 1
      misago/users/tests/test_user_middleware.py
  242. 6 7
      misago/users/tests/test_user_model.py
  243. 1 1
      misago/users/tests/test_user_requestdatadownload_api.py
  244. 1 1
      misago/users/tests/test_user_signature_api.py
  245. 2 2
      misago/users/tests/test_user_username_api.py
  246. 30 31
      misago/users/tests/test_useradmin_views.py
  247. 1 1
      misago/users/tests/test_usernamechanges_api.py
  248. 11 11
      misago/users/tests/test_users_api.py
  249. 1 2
      misago/users/tests/test_utils.py
  250. 2 3
      misago/users/tests/test_validators.py
  251. 1 1
      misago/users/testutils.py
  252. 1 2
      misago/users/tokens.py
  253. 1 1
      misago/users/validators.py
  254. 1 1
      misago/users/views/admin/bans.py
  255. 1 1
      misago/users/views/admin/datadownloads.py
  256. 1 1
      misago/users/views/admin/ranks.py
  257. 2 2
      misago/users/views/admin/users.py
  258. 5 4
      misago/users/views/auth.py
  259. 1 2
      misago/users/views/lists.py
  260. 1 2
      misago/users/views/options.py
  261. 1 2
      misago/users/views/profile.py
  262. 0 1
      requirements.in
  263. 0 1
      requirements.txt
  264. 0 3
      setup.py

+ 0 - 3
.travis.yml

@@ -4,9 +4,6 @@ addons:
   postgresql: "9.4"
 language: python
 python:
-  - "2.7"
-  - "3.4"
-  - "3.5"
   - "3.6"
 install:
   - pip install -U pip setuptools

+ 1 - 1
Dockerfile

@@ -1,7 +1,7 @@
 # This dockerfile is only meant for local development of Misago
 # If you are looking for a proper docker setup for running Misago in production,
 # please use misago-docker instead
-FROM python:3.5
+FROM python:3.6
 
 ENV PYTHONUNBUFFERED 1
 ENV IN_MISAGO_DOCKER 1

+ 2 - 2
README.rst

@@ -10,9 +10,9 @@ Misago
    :target: https://coveralls.io/github/rafalp/Misago?branch=master
    :alt: Test Coverage
 
-.. image:: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-blue.svg
+.. image:: https://img.shields.io/badge/python-3.6-blue.svg
    :target: https://travis-ci.org/rafalp/Misago
-   :alt: Works on Python 2.7, 3.5, 3,6
+   :alt: Works on Python 3.6
 
 .. image:: https://img.shields.io/badge/chat-on_discord-7289da.svg
    :target: https://discord.gg/fwvrZgB

+ 0 - 3
misago/acl/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.postgres.fields import JSONField
 from django.db import migrations, models
 

+ 0 - 3
misago/acl/migrations/0002_acl_version_tracker.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 from misago.acl.constants import ACL_CACHEBUSTER

+ 0 - 3
misago/acl/migrations/0003_default_roles.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

+ 2 - 4
misago/acl/models.py

@@ -1,6 +1,5 @@
 from django.contrib.postgres.fields import JSONField
 from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext as _
 
 from . import version as acl_version
@@ -10,7 +9,6 @@ def permissions_default():
     return {}
 
 
-@python_2_unicode_compatible
 class BaseRole(models.Model):
     name = models.CharField(max_length=255)
     special_role = models.CharField(max_length=255, null=True, blank=True)
@@ -25,11 +23,11 @@ class BaseRole(models.Model):
     def save(self, *args, **kwargs):
         if self.pk:
             acl_version.invalidate()
-        return super(BaseRole, self).save(*args, **kwargs)
+        return super().save(*args, **kwargs)
 
     def delete(self, *args, **kwargs):
         acl_version.invalidate()
-        return super(BaseRole, self).delete(*args, **kwargs)
+        return super().delete(*args, **kwargs)
 
 
 class Role(BaseRole):

+ 2 - 3
misago/acl/tests/test_providers.py

@@ -1,7 +1,6 @@
 from types import ModuleType
 
 from django.test import TestCase
-from django.utils import six
 
 from misago.acl.providers import PermissionProviders
 from misago.conf import settings
@@ -58,7 +57,7 @@ class PermissionProvidersTests(TestCase):
         self.assertEqual(len(providers_list), len(providers_setting))
 
         for extension, module in providers_list:
-            self.assertTrue(isinstance(extension, six.string_types))
+            self.assertTrue(isinstance(extension, str))
             self.assertEqual(type(module), ModuleType)
 
     def test_dict(self):
@@ -77,7 +76,7 @@ class PermissionProvidersTests(TestCase):
         self.assertEqual(len(providers_dict), len(providers_setting))
 
         for extension, module in providers_dict.items():
-            self.assertTrue(isinstance(extension, six.string_types))
+            self.assertTrue(isinstance(extension, str))
             self.assertEqual(type(module), ModuleType)
 
     def test_annotators(self):

+ 0 - 3
misago/admin/tests/test_admin_views.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.urls import reverse

+ 1 - 1
misago/admin/testutils.py

@@ -5,7 +5,7 @@ from misago.users.testutils import SuperUserTestCase
 
 class AdminTestCase(SuperUserTestCase):
     def setUp(self):
-        super(AdminTestCase, self).setUp()
+        super().setUp()
         self.login_admin(self.user)
 
     def login_admin(self, user):

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

@@ -1,9 +1,10 @@
+from urllib.parse import urlencode
+
 from django.contrib import messages
 from django.core.paginator import EmptyPage, Paginator
 from django.db import transaction
 from django.shortcuts import redirect
 from django.urls import reverse
-from django.utils.six.moves.urllib.parse import urlencode
 from django.utils.translation import ugettext_lazy as _
 
 from misago.core.exceptions import ExplicitFirstPage

+ 3 - 3
misago/categories/forms.py

@@ -25,7 +25,7 @@ class AdminCategoryFieldMixin(object):
 
         kwargs.setdefault('queryset', queryset)
 
-        super(AdminCategoryFieldMixin, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def _get_level_indicator(self, obj):
         level = getattr(obj, obj._mptt_meta.level_attr) - self.base_level
@@ -135,7 +135,7 @@ class CategoryFormBase(forms.ModelForm):
         return data
 
     def clean(self):
-        data = super(CategoryFormBase, self).clean()
+        data = super().clean()
         self.instance.set_name(data.get('name'))
         return data
 
@@ -185,7 +185,7 @@ class DeleteCategoryFormBase(forms.ModelForm):
         fields = []
 
     def clean(self):
-        data = super(DeleteCategoryFormBase, self).clean()
+        data = super().clean()
 
         if data.get('move_threads_to'):
             if data['move_threads_to'].pk == self.instance.pk:

+ 0 - 3
misago/categories/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import mptt.fields
 
 import django.db.models.deletion

+ 0 - 3
misago/categories/migrations/0002_default_categories.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 from misago.core.utils import slugify

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

+ 0 - 3
misago/categories/migrations/0004_category_last_thread.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import django.db.models.deletion
 from django.db import migrations, models
 

+ 0 - 3
misago/categories/migrations/0005_auto_20170303_2027.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.10.5 on 2017-03-03 20:27
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/categories/migrations/0006_moderation_queue_roles.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

+ 0 - 3
misago/categories/migrations/0007_best_answers_roles.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.9 on 2018-03-18 20:40
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

+ 2 - 5
misago/categories/models.py

@@ -2,8 +2,6 @@ from mptt.managers import TreeManager
 from mptt.models import MPTTModel, TreeForeignKey
 
 from django.db import models
-from django.utils import six
-from django.utils.encoding import python_2_unicode_compatible
 
 from misago.acl import version as acl_version
 from misago.acl.models import BaseRole
@@ -58,7 +56,6 @@ class CategoryManager(TreeManager):
         cache.delete(CACHE_NAME)
 
 
-@python_2_unicode_compatible
 class Category(MPTTModel):
     parent = TreeForeignKey(
         'self',
@@ -110,7 +107,7 @@ class Category(MPTTModel):
     objects = CategoryManager()
 
     def __str__(self):
-        return six.text_type(self.thread_type.get_category_name(self))
+        return str(self.thread_type.get_category_name(self))
 
     @property
     def thread_type(self):
@@ -119,7 +116,7 @@ class Category(MPTTModel):
     def delete(self, *args, **kwargs):
         Category.objects.clear_cache()
         acl_version.invalidate()
-        return super(Category, self).delete(*args, **kwargs)
+        return super().delete(*args, **kwargs)
 
     def synchronize(self):
         threads_queryset = self.thread_set.filter(is_hidden=False, is_unapproved=False)

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

@@ -342,7 +342,7 @@ class CategoryAdminDeleteViewTests(CategoryAdminTestCase):
           + Category F
         """
 
-        super(CategoryAdminDeleteViewTests, self).setUp()
+        super().setUp()
 
         self.root = Category.objects.root_category()
         self.first_category = Category.objects.get(slug='first-category')

+ 1 - 1
misago/categories/tests/test_category_model.py

@@ -47,7 +47,7 @@ class CategoryManagerTests(MisagoTestCase):
 
 class CategoryModelTests(MisagoTestCase):
     def setUp(self):
-        super(CategoryModelTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.all_categories()[:1][0]
 

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.categories.management.commands import fixcategoriestree
 from misago.categories.models import Category

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

@@ -1,9 +1,9 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.core.management import call_command
 from django.test import TestCase
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.categories.management.commands import prunecategories
 from misago.categories.models import Category

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.categories.management.commands import synchronizecategories
 from misago.categories.models import Category

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

@@ -21,7 +21,7 @@ class CategoriesUtilsTests(AuthenticatedUserTestCase):
           + Subcategory F
         """
 
-        super(CategoriesUtilsTests, self).setUp()
+        super().setUp()
         threadstore.clear()
 
         self.root = Category.objects.root_category()

+ 1 - 1
misago/categories/tests/test_views.py

@@ -52,7 +52,7 @@ class CategoryViewsTests(AuthenticatedUserTestCase):
 
 class CategoryAPIViewsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(CategoryAPIViewsTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
 

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

@@ -17,7 +17,7 @@ class CategoryAdmin(generic.AdminBaseMixin):
     message_404 = _("Requested category does not exist.")
 
     def get_target(self, kwargs):
-        target = super(CategoryAdmin, self).get_target(kwargs)
+        target = super().get_target(kwargs)
 
         threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
 

+ 3 - 4
misago/conf/hydrators.py

@@ -1,8 +1,7 @@
-import six
-
+# fixme: rename this moduleto serialize
 
 def hydrate_string(dry_value):
-    return six.text_type(dry_value) if dry_value else ''
+    return str(dry_value) if dry_value else ''
 
 
 def dehydrate_string(wet_value):
@@ -22,7 +21,7 @@ def hydrate_int(dry_value):
 
 
 def dehydrate_int(wet_value):
-    return six.text_type(wet_value)
+    return str(wet_value)
 
 
 def hydrate_list(dry_value):

+ 0 - 3
misago/conf/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import django.db.models.deletion
 from django.contrib.postgres.fields import JSONField
 from django.db import migrations, models

+ 4 - 4
misago/core/apipatch.py

@@ -98,16 +98,16 @@ class ApiPatch(object):
 
     def validate_action(self, action):
         if not action.get('op'):
-            raise InvalidAction(u"undefined op")
+            raise InvalidAction("undefined op")
 
         if action.get('op') not in ALLOWED_OPS:
-            raise InvalidAction(u'"%s" op is unsupported' % action.get('op'))
+            raise InvalidAction('"%s" op is unsupported' % action.get('op'))
 
         if not action.get('path'):
-            raise InvalidAction(u'"%s" op has to specify path' % action.get('op'))
+            raise InvalidAction('"%s" op has to specify path' % action.get('op'))
 
         if 'value' not in action:
-            raise InvalidAction(u'"%s" op has to specify value' % action.get('op'))
+            raise InvalidAction('"%s" op has to specify value' % action.get('op'))
 
     def dispatch_action(self, patch, request, target, action):
         for handler in self._actions:

+ 1 - 2
misago/core/exceptionhandler.py

@@ -3,7 +3,6 @@ from rest_framework.views import exception_handler as rest_exception_handler
 from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.urls import reverse
-from django.utils import six
 from social_core.exceptions import SocialAuthBaseException
 from social_core.utils import social_logger
 
@@ -29,7 +28,7 @@ def is_misago_exception(exception):
 def handle_ajax_error(request, exception):
     json = {
         'is_error': 1,
-        'message': six.text_type(exception.message),
+        'message': str(exception.message),
     }
     return JsonResponse(json, status=exception.code)
 

+ 0 - 3
misago/core/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/core/migrations/0002_basic_settings.py

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

+ 4 - 6
misago/core/pgutils.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django.core.paginator import Paginator
 from django.db.models import Index
 
@@ -13,7 +11,7 @@ class PgPartialIndex(Index):
             raise ValueError('partial index requires WHERE clause')
         self.where = where
 
-        super(PgPartialIndex, self).__init__(fields, name)
+        super().__init__(fields, name)
 
     def set_name_with_model(self, model):
         table_name = model._meta.db_table
@@ -52,15 +50,15 @@ class PgPartialIndex(Index):
                 'where': "'{}'".format(', '.join(where_items)),
             }
         else:
-            return super(PgPartialIndex, self).__repr__()
+            return super().__repr__()
 
     def deconstruct(self):
-        path, args, kwargs = super(PgPartialIndex, self).deconstruct()
+        path, args, kwargs = super().deconstruct()
         kwargs['where'] = self.where
         return path, args, kwargs
 
     def get_sql_create_template_values(self, model, schema_editor, using):
-        parameters = super(PgPartialIndex, self).get_sql_create_template_values(
+        parameters = super().get_sql_create_template_values(
             model, schema_editor, '')
         parameters['extra'] = self.get_sql_extra(model, schema_editor)
         return parameters

+ 1 - 3
misago/core/shortcuts.py

@@ -2,8 +2,6 @@ from rest_framework.response import Response
 
 from django.http import Http404
 
-import six
-
 
 def paginate(
         object_list,
@@ -82,7 +80,7 @@ def validate_slug(model, slug):
 
 
 def get_int_or_404(value):
-    if six.text_type(value).isdigit():
+    if str(value).isdigit():
         return int(value)
     else:
         raise Http404()

+ 1 - 2
misago/core/slugify.py

@@ -1,10 +1,9 @@
 from unidecode import unidecode
 
 from django.template.defaultfilters import slugify as django_slugify
-from django.utils import six
 
 
 def default(string):
-    string = six.text_type(string)
+    string = str(string)
     string = unidecode(string)
     return django_slugify(string.replace('_', ' ').strip())

+ 1 - 1
misago/core/templatetags/misago_absoluteurl.py

@@ -20,4 +20,4 @@ def absoluteurl(url_or_name, *args, **kwargs):
         if not url_or_name.startswith('/'):
             return url_or_name
     
-    return u'{}{}'.format(absolute_url_prefix, url_or_name)
+    return '{}{}'.format(absolute_url_prefix, url_or_name)

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

@@ -8,9 +8,9 @@ register = template.Library()
 @register.simple_tag
 def pagetitle(title, **kwargs):
     if 'page' in kwargs and kwargs['page'] > 1:
-        title += u" (%s)" % (_(u"page: %(page)s") % {'page': kwargs['page']})
+        title += " (%s)" % (_("page: %(page)s") % {'page': kwargs['page']})
 
     if 'parent' in kwargs:
-        title += u" | %s" % kwargs['parent']
+        title += " | %s" % kwargs['parent']
 
     return title

+ 1 - 1
misago/core/testproject/searchfilters.py

@@ -1,2 +1,2 @@
 def test_filter(search):
-    return search.replace(u'MMM', u'Marines, Marauders and Medics')
+    return search.replace('MMM', 'Marines, Marauders and Medics')

+ 5 - 5
misago/core/tests/test_apipatch.py

@@ -91,19 +91,19 @@ class ApiPatchTests(TestCase):
             try:
                 patch.validate_action(action)
             except InvalidAction as e:
-                self.assertEqual(e.args[0], u"undefined op")
+                self.assertEqual(e.args[0], "undefined op")
 
         # unsupported op
         try:
             patch.validate_action({'op': 'nope'})
         except InvalidAction as e:
-            self.assertEqual(e.args[0], u'"nope" op is unsupported')
+            self.assertEqual(e.args[0], '"nope" op is unsupported')
 
         # op lacking patch
         try:
             patch.validate_action({'op': 'add'})
         except InvalidAction as e:
-            self.assertEqual(e.args[0], u'"add" op has to specify path')
+            self.assertEqual(e.args[0], '"add" op has to specify path')
 
         # op lacking value
         try:
@@ -112,7 +112,7 @@ class ApiPatchTests(TestCase):
                 'path': 'yolo',
             })
         except InvalidAction as e:
-            self.assertEqual(e.args[0], u'"add" op has to specify value')
+            self.assertEqual(e.args[0], '"add" op has to specify value')
 
         # empty value is allowed
         try:
@@ -122,7 +122,7 @@ class ApiPatchTests(TestCase):
                 'value': '',
             })
         except InvalidAction as e:
-            self.assertEqual(e.args[0], u'"add" op has to specify value')
+            self.assertEqual(e.args[0], '"add" op has to specify value')
 
     def test_dispatch_action(self):
         """dispatch_action calls specified actions"""

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

@@ -20,7 +20,7 @@ class CacheBusterTests(MisagoTestCase):
 
 class CacheBusterCacheTests(MisagoTestCase):
     def setUp(self):
-        super(CacheBusterCacheTests, self).setUp()
+        super().setUp()
 
         self.cache_name = 'eric_the_fish'
         cachebuster.register(self.cache_name)

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

@@ -1,7 +1,6 @@
 import warnings
 
 from django.test import TestCase
-from django.utils import six
 
 from misago.core.deprecations import RemovedInMisagoWarning, warn
 
@@ -13,5 +12,5 @@ class DeprecationsTests(TestCase):
             warn("test warning")
 
             self.assertEqual(len(warning), 1)
-            self.assertEqual(six.text_type(warning[0].message), "test warning")
+            self.assertEqual(str(warning[0].message), "test warning")
             self.assertTrue(issubclass(warning[0].category, RemovedInMisagoWarning))

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

@@ -23,6 +23,6 @@ class YesNoSwitchTests(TestCase):
 
     def test_dontstripme_input_is_ignored(self):
         """YesNoSwitch returns valid values for invalid input"""
-        form = YesNoForm({'test_field': u'221'})
+        form = YesNoForm({'test_field': '221'})
         form.full_clean()
         self.assertFalse(form.cleaned_data.get('test_field'))

+ 0 - 4
misago/core/tests/test_utils.py

@@ -1,11 +1,7 @@
-#-*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.core.exceptions import PermissionDenied
 from django.test import TestCase
 from django.test.client import RequestFactory
 from django.urls import reverse
-from django.utils import six
 
 from misago.core.utils import (
     clean_return_path, format_plaintext_for_html, is_referer_local, is_request_to_misago,

+ 2 - 2
misago/core/testutils.py

@@ -12,9 +12,9 @@ class MisagoTestCase(TestCase):
         threadstore.clear()
 
     def setUp(self):
-        super(MisagoTestCase, self).setUp()
+        super().setUp()
         self.clear_state()
 
     def tearDown(self):
         self.clear_state()
-        super(MisagoTestCase, self).tearDown()
+        super().tearDown()

+ 1 - 2
misago/faker/englishcorpus.py

@@ -1,4 +1,3 @@
-import codecs
 import os
 import random
 
@@ -12,7 +11,7 @@ class EnglishCorpus(object):
         self._previous = None
 
         self.phrases = []
-        with codecs.open(phrases_file, "r", "utf-8") as f:
+        with open(phrases_file, "r") as f:
             for phrase in [l.strip() for l in f.readlines()]:
                 if min_length and len(phrase) < min_length:
                     continue

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

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import random
 import time
 

+ 2 - 2
misago/legal/forms.py

@@ -40,7 +40,7 @@ class AgreementForm(forms.ModelForm):
         fields = ['type', 'title', 'link', 'text', 'is_active']
 
     def clean(self):
-        data = super(AgreementForm, self).clean()
+        data = super().clean()
 
         if not data.get('link') and not data.get('text'):
             raise forms.ValidationError(_("Please fill in agreement link or text."))
@@ -48,7 +48,7 @@ class AgreementForm(forms.ModelForm):
         return data
 
     def save(self):
-        agreement = super(AgreementForm, self).save()
+        agreement = super().save()
         if agreement.is_active:
             set_agreement_as_active(agreement)
         Agreement.objects.invalidate_cache()

+ 0 - 3
misago/legal/migrations/0001_initial.py

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

+ 0 - 3
misago/legal/migrations/0002_agreement_useragreement.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.15 on 2018-08-15 20:58
-from __future__ import unicode_literals
-
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion

+ 0 - 3
misago/legal/migrations/0003_create_agreements_from_settings.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.15 on 2018-08-16 14:22
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 from misago.conf.migrationutils import migrate_settings_group

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

@@ -8,7 +8,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class SubmitAgreementTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(SubmitAgreementTests, self).setUp()
+        super().setUp()
 
         self.agreement = Agreement.objects.create(
             type=Agreement.TYPE_TOS,

+ 2 - 2
misago/legal/tests/test_context_processors.py

@@ -16,7 +16,7 @@ class MockRequest(object):
 
 class PrivacyPolicyTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PrivacyPolicyTests, self).setUp()
+        super().setUp()
 
         Agreement.objects.invalidate_cache()
 
@@ -102,7 +102,7 @@ class PrivacyPolicyTests(AuthenticatedUserTestCase):
 
 class TermsOfServiceTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(TermsOfServiceTests, self).setUp()
+        super().setUp()
         
         Agreement.objects.invalidate_cache()
 

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

@@ -6,7 +6,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class RequiredAgreementTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(RequiredAgreementTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse('misago:index')
 

+ 3 - 3
misago/legal/views/admin.py

@@ -40,7 +40,7 @@ class AgreementsList(AgreementAdmin, generic.ListView):
     }, )
 
     def get_queryset(self):
-        qs = super(AgreementsList, self).get_queryset()
+        qs = super().get_queryset()
         return qs.select_related()
 
     def action_delete(self, request, items):
@@ -53,7 +53,7 @@ class NewAgreement(AgreementAdmin, generic.ModelFormView):
     message_submit = _('New agreement "%(title)s" has been saved.')
     
     def handle_form(self, form, request, target):
-        super(NewAgreement, self).handle_form(form, request, target)
+        super().handle_form(form, request, target)
 
         form.instance.set_created_by(request.user)
         form.instance.save()
@@ -63,7 +63,7 @@ class EditAgreement(AgreementAdmin, generic.ModelFormView):
     message_submit = _('Agreement "%(title)s" has been edited.')
 
     def handle_form(self, form, request, target):
-        super(EditAgreement, self).handle_form(form, request, target)
+        super().handle_form(form, request, target)
 
         form.instance.last_modified_on = timezone.now()
         form.instance.set_last_modified_by(request.user)

+ 1 - 3
misago/markup/bbcode/blocks.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import re
 
 import markdown
@@ -68,7 +66,7 @@ class QuotePreprocessor(Preprocessor):
 
 class QuoteBlockProcessor(BlockProcessor):
     def __init__(self, *args, **kwargs):
-        super(QuoteBlockProcessor, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self._title = None
         self._quote = 0
         self._children = []

+ 1 - 3
misago/markup/checksums.py

@@ -22,12 +22,10 @@ in char fields with max_length=64
 """
 from hashlib import sha256
 
-from django.utils import six
-
 
 def make_checksum(parsed, unique_values=None):
     unique_values = unique_values or []
-    seeds = [parsed] + [six.text_type(v) for v in unique_values]
+    seeds = [parsed] + [str(v) for v in unique_values]
 
     return sha256('+'.join(seeds).encode("utf-8")).hexdigest()
 

+ 0 - 2
misago/markup/finalise.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import re
 
 from django.utils.translation import ugettext as _

+ 2 - 3
misago/markup/mentions.py

@@ -3,7 +3,6 @@ import re
 from bs4 import BeautifulSoup
 
 from django.contrib.auth import get_user_model
-from django.utils import six
 
 
 SUPPORTED_TAGS = ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'p')
@@ -26,7 +25,7 @@ def add_mentions(request, result):
     for element in elements:
         add_mentions_to_element(request, element, mentions_dict)
 
-    result['parsed_text'] = six.text_type(soup.body)[6:-7].strip()
+    result['parsed_text'] = str(soup.body)[6:-7].strip()
     result['mentions'] = list(filter(bool, mentions_dict.values()))
 
 
@@ -59,7 +58,7 @@ def parse_string(request, element, mentions_dict):
 
         if mentions_dict[username]:
             user = mentions_dict[username]
-            return u'<a href="{}">@{}</a>'.format(user.get_absolute_url(), user.username)
+            return '<a href="{}">@{}</a>'.format(user.get_absolute_url(), user.username)
         else:
             # we've failed to resolve user for username
             return matchobj.group(0)

+ 7 - 6
misago/markup/parser.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import warnings
 
 import bleach
@@ -10,7 +8,6 @@ from markdown.extensions.fenced_code import FencedCodeExtension
 
 from django.http import Http404
 from django.urls import resolve
-from django.utils import six
 
 from misago.conf import settings
 
@@ -188,7 +185,7 @@ def clean_links(request, result, force_shva=False):
             img['src'] = assert_link_prefix(img['src'])
 
     # [6:-7] trims <body></body> wrap
-    result['parsed_text'] = six.text_type(soup.body)[6:-7]
+    result['parsed_text'] = str(soup.body)[6:-7]
 
 
 def is_internal_link(link, host):
@@ -250,6 +247,10 @@ def clean_attachment_link(link, force_shva=False):
 
 
 def minify_result(result):
+    result['parsed_text'] = html_minify(result['parsed_text'])
+    result['parsed_text'] = strip_html_head_body(result['parsed_text'])
+
+
+def strip_html_head_body(parsed_text):
     # [25:-14] trims <html><head></head><body> and </body></html>
-    result['parsed_text'] = html_minify(result['parsed_text'].encode('utf-8'))
-    result['parsed_text'] = result['parsed_text'][25:-14]
+    return parsed_text[25:-14]

+ 1 - 3
misago/markup/pipeline.py

@@ -2,8 +2,6 @@ from importlib import import_module
 
 from bs4 import BeautifulSoup
 
-from django.utils import six
-
 from misago.conf import settings
 
 
@@ -26,7 +24,7 @@ class MarkupPipeline(object):
                 hook = getattr(module, 'clean_parsed')
                 hook.process_result(result, soup)
 
-        souped_text = six.text_type(soup.body).strip()[6:-7]
+        souped_text = str(soup.body).strip()[6:-7]
         result['parsed_text'] = souped_text.strip()
         return result
 

+ 1 - 4
misago/markup/tests/test_api.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.users.testutils import AuthenticatedUserTestCase
@@ -8,7 +5,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ParseMarkupApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ParseMarkupApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:parse-markup')
 

+ 0 - 3
misago/markup/tests/test_finalise.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.test import TestCase
 
 from misago.markup.finalise import finalise_markup

+ 0 - 3
misago/markup/tests/test_parser.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.test import TestCase
 

+ 0 - 3
misago/readtracker/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import django.db.models.deletion
 from django.conf import settings
 from django.db import migrations, models

+ 0 - 3
misago/readtracker/migrations/0002_postread.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.5 on 2017-10-07 14:32
-from __future__ import unicode_literals
-
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion

+ 0 - 3
misago/readtracker/migrations/0003_migrate_reads_to_posts.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.5 on 2017-10-07 14:49
-from __future__ import unicode_literals
-
 from datetime import timedelta
 
 from django.db import migrations

+ 0 - 3
misago/readtracker/migrations/0004_auto_20171015_2010.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.5 on 2017-10-15 20:10
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

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

@@ -1,10 +1,10 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.categories.models import Category
 from misago.conf import settings

+ 1 - 2
misago/search/api.py

@@ -5,7 +5,6 @@ from rest_framework.response import Response
 
 from django.core.exceptions import PermissionDenied
 from django.urls import reverse
-from django.utils import six
 from django.utils.translation import ugettext as _
 
 from misago.core.shortcuts import get_int_or_404
@@ -24,7 +23,7 @@ def search(request, search_provider=None):
     for provider in allowed_providers:
         provider_data = {
             'id': provider.url,
-            'name': six.text_type(provider.name),
+            'name': str(provider.name),
             'icon': provider.icon,
             'url': reverse('misago:search', kwargs={'search_provider': provider.url}),
             'api': reverse('misago:api:search', kwargs={'search_provider': provider.url}),

+ 1 - 2
misago/search/context_processors.py

@@ -1,6 +1,5 @@
 from django.core.exceptions import PermissionDenied
 from django.urls import reverse
-from django.utils import six
 
 from .searchproviders import searchproviders
 
@@ -25,7 +24,7 @@ def search_providers(request):
     for provider in allowed_providers:
         request.frontend_context['SEARCH_PROVIDERS'].append({
             'id': provider.url,
-            'name': six.text_type(provider.name),
+            'name': str(provider.name),
             'icon': provider.icon,
             'url': reverse('misago:search', kwargs={'search_provider': provider.url}),
             'api': reverse('misago:api:search', kwargs={'search_provider': provider.url}),

+ 3 - 4
misago/search/tests/test_api.py

@@ -1,5 +1,4 @@
 from django.urls import reverse
-from django.utils import six
 
 from misago.acl.testutils import override_acl
 from misago.search.searchproviders import searchproviders
@@ -8,7 +7,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class SearchApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(SearchApiTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse('misago:api:search')
 
@@ -34,7 +33,7 @@ class SearchApiTests(AuthenticatedUserTestCase):
             )
             self.assertEqual(provider_api, provider['api'])
 
-            self.assertEqual(six.text_type(providers[i].name), provider['name'])
+            self.assertEqual(str(providers[i].name), provider['name'])
             self.assertEqual(provider['results']['results'], [])
             self.assertEqual(int(provider['time']), 0)
 
@@ -51,6 +50,6 @@ class SearchApiTests(AuthenticatedUserTestCase):
             )
             self.assertEqual(provider_api, provider['api'])
 
-            self.assertEqual(six.text_type(providers[i].name), provider['name'])
+            self.assertEqual(str(providers[i].name), provider['name'])
             self.assertEqual(provider['results']['results'], [])
             self.assertEqual(int(provider['time']), 0)

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

@@ -7,7 +7,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class LandingTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(LandingTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse('misago:search')
 

+ 0 - 1
misago/search/views.py

@@ -2,7 +2,6 @@ from django.core.exceptions import PermissionDenied
 from django.http import Http404
 from django.shortcuts import redirect, render
 from django.urls import reverse
-from django.utils import six
 from django.utils.translation import ugettext as _
 
 from .searchproviders import searchproviders

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

@@ -1,7 +1,6 @@
 from rest_framework.response import Response
 
 from django.core.exceptions import PermissionDenied
-from django.utils import six
 from django.utils.translation import ugettext as _
 
 from misago.threads.models import Thread

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

@@ -51,7 +51,7 @@ class CategorySerializer(serializers.Serializer):
         self.user = user
         self.category_cache = None
 
-        super(CategorySerializer, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def validate_category(self, value):
         try:

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

@@ -8,7 +8,7 @@ from . import PostingEndpoint, PostingMiddleware
 
 class EmailNotificationMiddleware(PostingMiddleware):
     def __init__(self, **kwargs):
-        super(EmailNotificationMiddleware, self).__init__(**kwargs)
+        super().__init__(**kwargs)
 
         self.previous_last_post_on = self.thread.last_post_on
 

+ 2 - 4
misago/threads/api/postingendpoint/participants.py

@@ -2,9 +2,7 @@ from rest_framework import serializers
 
 from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
-from django.utils import six
-from django.utils.translation import ugettext as _
-from django.utils.translation import ungettext
+from django.utils.translation import ugettext as _, ungettext
 
 from misago.categories import PRIVATE_THREADS_ROOT_NAME
 from misago.threads.participants import add_participants, set_owner
@@ -75,7 +73,7 @@ class ParticipantsSerializer(serializers.Serializer):
             try:
                 allow_message_user(self.context['user'], user)
             except PermissionDenied as e:
-                raise serializers.ValidationError(six.text_type(e))
+                raise serializers.ValidationError(str(e))
             users.append(user)
 
         if len(usernames) != len(users):

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

@@ -7,7 +7,7 @@ from . import PostingMiddleware
 
 class SaveChangesMiddleware(PostingMiddleware):
     def __init__(self, **kwargs):
-        super(SaveChangesMiddleware, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.reset_state()
 
     def reset_state(self):

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

@@ -2,7 +2,6 @@ from rest_framework.exceptions import ValidationError
 from rest_framework.response import Response
 
 from django.core.exceptions import PermissionDenied
-from django.utils.six import text_type
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
@@ -114,7 +113,7 @@ def threads_merge_endpoint(request):
             invalid_threads.append({
                 'id': thread.pk,
                 'title': thread.title,
-                'errors': [text_type(e)]
+                'errors': [str(e)]
             })
 
     if invalid_threads:

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

@@ -5,7 +5,6 @@ from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied, ValidationError
 from django.http import Http404
 from django.shortcuts import get_object_or_404
-from django.utils import six
 from django.utils.translation import ugettext as _
 
 from misago.acl import add_acl
@@ -49,7 +48,7 @@ thread_patch_dispatcher.add('acl', patch_acl)
 
 def patch_title(request, thread, value):
     try:
-        value_cleaned = six.text_type(value).strip()
+        value_cleaned = str(value).strip()
     except (TypeError, ValueError):
         raise PermissionDenied(_('Not a valid string.'))
 
@@ -272,7 +271,7 @@ def patch_add_participant(request, thread, value):
     allow_add_participants(request.user, thread)
 
     try:
-        username = six.text_type(value).strip().lower()
+        username = str(value).strip().lower()
         if not username:
             raise PermissionDenied(_("You have to enter new participant's username."))
         participant = UserModel.objects.get(slug=username)

+ 1 - 3
misago/threads/checksums.py

@@ -1,5 +1,3 @@
-from django.utils import six
-
 from misago.markup import checksums
 
 
@@ -9,7 +7,7 @@ def is_post_valid(post):
 
 
 def make_post_checksum(post):
-    post_seeds = [six.text_type(v) for v in (post.id, str(post.posted_on.date()))]
+    post_seeds = [str(v) for v in (post.id, str(post.posted_on.date()))]
     return checksums.make_checksum(post.parsed, post_seeds)
 
 

+ 1 - 1
misago/threads/mergeconflict.py

@@ -75,7 +75,7 @@ class PollMergeHandler(MergeConflictHandler):
     def get_available_resolutions(self):
         resolutions = [[0, _("Delete all polls")]]
         for poll in self.items:
-            resolutions.append([poll.id, u'{} ({})'.format(poll.question, poll.thread.title)])
+            resolutions.append([poll.id, '{} ({})'.format(poll.question, poll.thread.title)])
         return resolutions
 
 

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import django.db.models.deletion
 import django.utils.timezone
 from django.conf import settings

+ 0 - 3
misago/threads/migrations/0002_threads_settings.py

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

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

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.9.7 on 2016-10-04 21:41
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

+ 0 - 3
misago/threads/migrations/0004_update_settings.py

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

+ 0 - 3
misago/threads/migrations/0005_index_search_document.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.1 on 2017-05-21 17:52
-from __future__ import unicode_literals
-
 import django.contrib.postgres.indexes
 from django.contrib.postgres.operations import BtreeGinExtension
 from django.db import migrations

+ 0 - 3
misago/threads/migrations/0006_redo_partial_indexes.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.1 on 2017-05-21 17:52
-from __future__ import unicode_literals
-
 import django.contrib.postgres.indexes
 from django.contrib.postgres.operations import BtreeGinExtension
 from django.db import migrations

+ 0 - 3
misago/threads/migrations/0007_auto_20171008_0131.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.5 on 2017-10-08 01:31
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/threads/migrations/0008_auto_20180310_2234.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.9 on 2018-03-10 22:34
-from __future__ import unicode_literals
-
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion

+ 1 - 3
misago/threads/migrations/0009_auto_20180326_0010.py

@@ -1,8 +1,6 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.9 on 2018-03-26 00:10
-from __future__ import unicode_literals
-
 from django.db import migrations
+
 import misago.core.pgutils
 
 

+ 0 - 3
misago/threads/migrations/0010_auto_20180609_1523.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.13 on 2018-06-09 15:23
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 

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

@@ -10,7 +10,6 @@ from django.db import models
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.crypto import get_random_string
-from django.utils.encoding import python_2_unicode_compatible
 
 from misago.conf import settings
 from misago.core.utils import slugify
@@ -30,7 +29,6 @@ def upload_to(instance, filename):
     return os.path.join('attachments', spread_path[:2], spread_path[2:4], secret, filename_clean)
 
 
-@python_2_unicode_compatible
 class Attachment(models.Model):
     secret = models.CharField(max_length=64)
     filetype = models.ForeignKey('AttachmentType', on_delete=models.CASCADE)
@@ -59,7 +57,7 @@ class Attachment(models.Model):
 
     def delete(self, *args, **kwargs):
         self.delete_files()
-        return super(Attachment, self).delete(*args, **kwargs)
+        return super().delete(*args, **kwargs)
 
     def delete_files(self):
         if self.thumbnail:

+ 0 - 4
misago/threads/models/attachmenttype.py

@@ -1,11 +1,7 @@
-from __future__ import unicode_literals
-
 from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 
-@python_2_unicode_compatible
 class AttachmentType(models.Model):
     ENABLED = 0
     LOCKED = 1

+ 5 - 9
misago/threads/models/post.py

@@ -1,13 +1,10 @@
-from __future__ import unicode_literals
-
 import copy
 
 from django.contrib.postgres.indexes import GinIndex
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.search import SearchVector, SearchVectorField
 from django.db import models
-from django.utils import six, timezone
-from django.utils.encoding import python_2_unicode_compatible
+from django.utils import timezone
 
 from misago.conf import settings
 from misago.core.pgutils import PgPartialIndex
@@ -17,7 +14,6 @@ from misago.threads.checksums import is_post_valid, update_post_checksum
 from misago.threads.filtersearch import filter_search
 
 
-@python_2_unicode_compatible
 class Post(models.Model):
     category = models.ForeignKey(
         'misago_categories.Category',
@@ -118,7 +114,7 @@ class Post(models.Model):
         from misago.threads.signals import delete_post
         delete_post.send(sender=self)
 
-        super(Post, self).delete(*args, **kwargs)
+        super().delete(*args, **kwargs)
 
     def merge(self, other_post):
         if self.poster_id != other_post.poster_id:
@@ -136,8 +132,8 @@ class Post(models.Model):
         if self.pk == other_post.pk:
             raise ValueError("post can't be merged with itself")
 
-        other_post.original = six.text_type('\n\n').join((other_post.original, self.original))
-        other_post.parsed = six.text_type('\n').join((other_post.parsed, self.parsed))
+        other_post.original = str('\n\n').join((other_post.original, self.original))
+        other_post.parsed = str('\n').join((other_post.parsed, self.parsed))
         update_post_checksum(other_post)
 
         if self.is_protected:
@@ -217,7 +213,7 @@ class Post(models.Model):
     def short(self):
         if self.is_valid:
             if len(self.original) > 150:
-                return six.text_type('%s...') % self.original[:150].strip()
+                return str('%s...') % self.original[:150].strip()
             else:
                 return self.original
         else:

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

@@ -1,7 +1,6 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import models
 from django.utils import timezone
-from django.utils.encoding import python_2_unicode_compatible
 from django.utils.translation import ugettext_lazy as _
 
 from misago.conf import settings
@@ -9,7 +8,6 @@ from misago.core.pgutils import PgPartialIndex
 from misago.core.utils import slugify
 
 
-@python_2_unicode_compatible
 class Thread(models.Model):
     WEIGHT_DEFAULT = 0
     WEIGHT_PINNED = 1
@@ -150,7 +148,7 @@ class Thread(models.Model):
         from misago.threads.signals import delete_thread
         delete_thread.send(sender=self)
 
-        super(Thread, self).delete(*args, **kwargs)
+        super().delete(*args, **kwargs)
 
     def merge(self, other_thread):
         if self.pk == other_thread.pk:

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

@@ -1,7 +1,3 @@
-from django.utils.encoding import python_2_unicode_compatible
-
-
-@python_2_unicode_compatible
 class ModerationError(Exception):
     def __init__(self, message):
         self.message = message

+ 1 - 2
misago/threads/paginator.py

@@ -8,8 +8,7 @@ class PostsPaginator(Paginator):
         per_page = int(per_page) - 1
         if orphans:
             orphans += 1
-        super(PostsPaginator,
-              self).__init__(object_list, per_page, orphans, allow_empty_first_page)
+        super().__init__(object_list, per_page, orphans, allow_empty_first_page)
 
     def page(self, number):
         """returns a Page object for the given 1-based page number."""

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

@@ -2,7 +2,6 @@ from rest_framework import serializers
 
 from django.core.exceptions import PermissionDenied, ValidationError
 from django.http import Http404
-from django.utils import six
 from django.utils.translation import ugettext as _, ugettext_lazy, ungettext
 
 from misago.acl import add_acl
@@ -398,7 +397,7 @@ class DeleteThreadsSerializer(serializers.Serializer):
                         'id': thread.id,
                         'title': thread.title
                     },
-                    'error': six.text_type(e)
+                    'error': str(e)
                 })
             except Http404 as e:
                 pass # skip invisible threads

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

@@ -160,7 +160,7 @@ class EditPollSerializer(serializers.ModelSerializer):
         if instance.choices:
             self.update_choices(instance, validated_data['choices'])
 
-        return super(EditPollSerializer, self).update(instance, validated_data)
+        return super().update(instance, validated_data)
 
     def update_choices(self, instance, cleaned_choices):
         removed_hashes = []

+ 1 - 1
misago/threads/signals.py

@@ -175,7 +175,7 @@ def archive_user_polls(sender, archive=None, **kwargs):
             item_name,
             OrderedDict([
                 (_("Question"), poll.question),
-                (_("Choices"), u', '.join([c['label'] for c in poll.choices])),
+                (_("Choices"), ', '.join([c['label'] for c in poll.choices])),
             ]),
             date=poll.posted_on,
         )

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

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django import template
 from django.utils.translation import ugettext as _
 from django.utils.translation import ungettext

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

@@ -22,7 +22,7 @@ def get_mock_user():
 
 class AnonymizeEventsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AnonymizeEventsTests, self).setUp()
+        super().setUp()
         self.factory = RequestFactory()
 
         category = Category.objects.get(slug='first-category')
@@ -173,7 +173,7 @@ class AnonymizeEventsTests(AuthenticatedUserTestCase):
 
 class AnonymizeLikesTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AnonymizeLikesTests, self).setUp()
+        super().setUp()
         self.factory = RequestFactory()
 
     def get_request(self, user=None):
@@ -212,7 +212,7 @@ class AnonymizeLikesTests(AuthenticatedUserTestCase):
 
 class AnonymizePostsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AnonymizePostsTests, self).setUp()
+        super().setUp()
         self.factory = RequestFactory()
 
     def get_request(self, user=None):

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

@@ -8,7 +8,7 @@ from misago.threads.models import Attachment, AttachmentType
 
 class AttachmentAdminViewsTests(AdminTestCase):
     def setUp(self):
-        super(AttachmentAdminViewsTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.post = testutils.post_thread(category=self.category).first_post

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

@@ -3,7 +3,6 @@ import os
 from PIL import Image
 
 from django.urls import reverse
-from django.utils import six
 
 from misago.acl.models import Role
 from misago.acl.testutils import override_acl
@@ -22,7 +21,7 @@ TEST_CORRUPTEDIMG_PATH = os.path.join(TESTFILES_DIR, 'corrupted.gif')
 
 class AttachmentsApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AttachmentsApiTestCase, self).setUp()
+        super().setUp()
 
         AttachmentType.objects.all().delete()
 
@@ -218,7 +217,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.assertTrue(not attachment.image)
         self.assertTrue(not attachment.thumbnail)
 
-        self.assertTrue(six.text_type(attachment.file).endswith('document.pdf'))
+        self.assertTrue(str(attachment.file).endswith('document.pdf'))
 
         self.assertIsNone(response_json['post'])
         self.assertEqual(response_json['uploader_name'], self.user.username)
@@ -261,7 +260,7 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.assertIsNotNone(attachment.image)
         self.assertTrue(not attachment.thumbnail)
 
-        self.assertTrue(six.text_type(attachment.image).endswith('small.jpg'))
+        self.assertTrue(str(attachment.image).endswith('small.jpg'))
 
         self.assertIsNone(response_json['post'])
         self.assertEqual(response_json['uploader_name'], self.user.username)
@@ -300,8 +299,8 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.assertIsNotNone(attachment.image)
         self.assertIsNotNone(attachment.thumbnail)
 
-        self.assertTrue(six.text_type(attachment.image).endswith('large.png'))
-        self.assertTrue(six.text_type(attachment.thumbnail).endswith('large.png'))
+        self.assertTrue(str(attachment.image).endswith('large.png'))
+        self.assertTrue(str(attachment.thumbnail).endswith('large.png'))
 
         self.assertIsNone(response_json['post'])
         self.assertEqual(response_json['uploader_name'], self.user.username)
@@ -355,8 +354,8 @@ class AttachmentsApiTestCase(AuthenticatedUserTestCase):
         self.assertIsNotNone(attachment.image)
         self.assertIsNotNone(attachment.thumbnail)
 
-        self.assertTrue(six.text_type(attachment.image).endswith('animated.gif'))
-        self.assertTrue(six.text_type(attachment.thumbnail).endswith('animated.gif'))
+        self.assertTrue(str(attachment.image).endswith('animated.gif'))
+        self.assertTrue(str(attachment.thumbnail).endswith('animated.gif'))
 
         self.assertIsNone(response_json['post'])
         self.assertEqual(response_json['uploader_name'], self.user.username)

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

@@ -18,7 +18,7 @@ class RequestMock(object):
 
 class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AttachmentsMiddlewareTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -7,7 +7,7 @@ from misago.threads.models import AttachmentType
 
 class AttachmentTypeAdminViewsTests(AdminTestCase):
     def setUp(self):
-        super(AttachmentTypeAdminViewsTests, self).setUp()
+        super().setUp()
         self.admin_link = reverse('misago:admin:system:attachment-types:index')
 
     def test_link_registered(self):

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

@@ -18,7 +18,7 @@ TEST_SMALLJPG_PATH = os.path.join(TESTFILES_DIR, 'small.jpg')
 
 class AttachmentViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(AttachmentViewTestCase, self).setUp()
+        super().setUp()
 
         AttachmentType.objects.all().delete()
 

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

@@ -1,9 +1,9 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.core.management import call_command
 from django.test import TestCase
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.categories.models import Category
 from misago.conf import settings

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

@@ -19,7 +19,7 @@ def get_mock_user():
 
 class DeleteUserLikesTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(DeleteUserLikesTests, self).setUp()
+        super().setUp()
         self.factory = RequestFactory()
 
     def get_request(self, user=None):

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from copy import deepcopy
 from datetime import timedelta
 
@@ -21,7 +18,7 @@ UserModel = get_user_model()
 
 class EmailNotificationTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(EmailNotificationTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(

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

@@ -1,4 +1,3 @@
-#-*- coding: utf-8 -*-
 from django.contrib.auth import get_user_model
 from django.test import TestCase
 from django.utils import timezone

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.acl.testutils import override_acl
@@ -11,7 +8,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class PostMentionsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PostMentionsTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -14,7 +14,7 @@ GOTO_PAGE_URL = '%s%s/#post-%s'
 
 class GotoViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(GotoViewTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -174,7 +174,7 @@ class MergeConflictTests(TestCase):
                 'polls': [['0', 'Delete all polls']] + [
                     [
                         str(thread.poll.id),
-                        u'{} ({})'.format(thread.poll.question, thread.title),
+                        '{} ({})'.format(thread.poll.question, thread.title),
                     ] for thread in polls
                 ]
             })
@@ -238,7 +238,7 @@ class MergeConflictTests(TestCase):
                 'polls': [['0', 'Delete all polls']] + [
                     [
                         str(thread.poll.id),
-                        u'{} ({})'.format(thread.poll.question, thread.title),
+                        '{} ({})'.format(thread.poll.question, thread.title),
                     ] for thread in polls
                 ]
             })

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart
 from django.urls import reverse
@@ -17,7 +14,7 @@ UserModel = get_user_model()
 
 class PostMentionsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PostMentionsTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -6,7 +6,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class PostsModerationTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PostsModerationTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.all_categories()[:1][0]
         self.thread = testutils.post_thread(self.category)

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

@@ -15,7 +15,7 @@ UserModel = get_user_model()
 
 class PrivateThreadPatchApiTestCase(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadPatchApiTestCase, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()

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

@@ -11,7 +11,7 @@ UserModel = get_user_model()
 
 class PrivateThreadReplyApiTestCase(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadReplyApiTestCase, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_posts_api_url()

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.core import mail
 from django.urls import reverse
@@ -17,7 +14,7 @@ UserModel = get_user_model()
 
 class StartPrivateThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(StartPrivateThreadTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.private_threads()
         self.api_link = reverse('misago:api:private-thread-list')

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

@@ -7,7 +7,7 @@ from .test_privatethreads import PrivateThreadsTestCase
 
 class PrivateThreadViewTests(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadViewTests, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.test_link = self.thread.get_absolute_url()

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

@@ -5,7 +5,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class PrivateThreadsTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PrivateThreadsTestCase, self).setUp()
+        super().setUp()
         self.category = Category.objects.private_threads()
 
         override_acl(self.user, {

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

@@ -9,7 +9,7 @@ from .test_privatethreads import PrivateThreadsTestCase
 
 class PrivateThreadsListApiTests(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadsListApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:private-thread-list')
 
@@ -69,7 +69,7 @@ class PrivateThreadsListApiTests(PrivateThreadsTestCase):
 
 class PrivateThreadRetrieveApiTests(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadRetrieveApiTests, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()
@@ -167,7 +167,7 @@ class PrivateThreadRetrieveApiTests(PrivateThreadsTestCase):
 
 class PrivateThreadDeleteApiTests(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(self.category, poster=self.user)
         self.api_link = self.thread.get_api_url()

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

@@ -9,7 +9,7 @@ from .test_privatethreads import PrivateThreadsTestCase
 
 class PrivateThreadsListTests(PrivateThreadsTestCase):
     def setUp(self):
-        super(PrivateThreadsListTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse('misago:private-threads')
 

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

@@ -7,7 +7,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class SearchApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(SearchApiTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
 
@@ -209,7 +209,7 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
 class SearchProviderApiTests(SearchApiTests):
     def setUp(self):
-        super(SearchProviderApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse(
             'misago:api:search', kwargs={

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.urls import reverse
 
@@ -15,7 +12,7 @@ UserModel = get_user_model()
 
 class SubscriptionMiddlewareTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(SubscriptionMiddlewareTestCase, self).setUp()
+        super().setUp()
         self.category = Category.objects.get(slug='first-category')
         self.override_acl()
 
@@ -34,7 +31,7 @@ class SubscriptionMiddlewareTestCase(AuthenticatedUserTestCase):
 
 class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
     def setUp(self):
-        super(SubscribeStartedThreadTests, self).setUp()
+        super().setUp()
         self.api_link = reverse('misago:api:thread-list')
 
     def test_dont_subscribe(self):
@@ -103,7 +100,7 @@ class SubscribeStartedThreadTests(SubscriptionMiddlewareTestCase):
 
 class SubscribeRepliedThreadTests(SubscriptionMiddlewareTestCase):
     def setUp(self):
-        super(SubscribeRepliedThreadTests, self).setUp()
+        super().setUp()
         self.thread = testutils.post_thread(self.category)
         self.api_link = reverse(
             'misago:api:thread-post-list', kwargs={

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

@@ -11,7 +11,7 @@ UserModel = get_user_model()
 
 class SyncUnreadPrivateThreadsTestCase(PrivateThreadsTestCase):
     def setUp(self):
-        super(SyncUnreadPrivateThreadsTestCase, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user(
             'BobBoberson', 'bob@boberson.com', 'pass123'

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.categories.models import Category
 from misago.threads import testutils

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

@@ -12,7 +12,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadsBulkPatchApiTestCase(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadsBulkPatchApiTestCase, self).setUp()
+        super().setUp()
 
         self.threads = list(reversed([
             testutils.post_thread(category=self.category),
@@ -243,7 +243,7 @@ class BulkThreadChangeTitleApiTests(ThreadsBulkPatchApiTestCase):
 
 class BulkThreadMoveApiTests(ThreadsBulkPatchApiTestCase):
     def setUp(self):
-        super(BulkThreadMoveApiTests, self).setUp()
+        super().setUp()
 
         Category(
             name='Category B',

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from datetime import timedelta
 
 from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class EditReplyTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(EditReplyTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -11,7 +11,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadMergeApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadMergeApiTests, self).setUp()
+        super().setUp()
 
         Category(
             name='Category B',
@@ -708,11 +708,11 @@ class ThreadMergeApiTests(ThreadsApiTestCase):
                     ['0', "Delete all polls"],
                     [
                         str(poll.pk),
-                        u'{} ({})'.format(poll.question, poll.thread.title),
+                        '{} ({})'.format(poll.question, poll.thread.title),
                     ],
                     [
                         str(other_poll.pk),
-                        u'{} ({})'.format(other_poll.question, other_poll.thread.title),
+                        '{} ({})'.format(other_poll.question, other_poll.thread.title),
                     ],
                 ]
             }

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

@@ -1,7 +1,7 @@
 import json
 from datetime import timedelta
 
-from django.utils import six, timezone
+from django.utils import timezone
 
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
@@ -443,7 +443,7 @@ class ThreadPinLocallyApiTests(ThreadPatchApiTestCase):
 
 class ThreadMoveApiTests(ThreadPatchApiTestCase):
     def setUp(self):
-        super(ThreadMoveApiTests, self).setUp()
+        super().setUp()
 
         Category(
             name='Category B',
@@ -1204,7 +1204,7 @@ class ThreadHideApiTests(ThreadPatchApiTestCase):
 
 class ThreadUnhideApiTests(ThreadPatchApiTestCase):
     def setUp(self):
-        super(ThreadUnhideApiTests, self).setUp()
+        super().setUp()
 
         self.thread.is_hidden = True
         self.thread.save()
@@ -1388,7 +1388,7 @@ class ThreadSubscribeApiTests(ThreadPatchApiTestCase):
     def test_subscribe_nonexistant_thread(self):
         """api makes it impossible to subscribe nonexistant thread"""
         bad_api_link = self.api_link.replace(
-            six.text_type(self.thread.pk), six.text_type(self.thread.pk + 9)
+            str(self.thread.pk), str(self.thread.pk + 9)
         )
 
         response = self.patch(
@@ -1857,7 +1857,7 @@ class ThreadMarkBestAnswerApiTests(ThreadPatchApiTestCase):
 
 class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
     def setUp(self):
-        super(ThreadChangeBestAnswerApiTests, self).setUp()
+        super().setUp()
 
         self.best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, self.best_answer)
@@ -2150,7 +2150,7 @@ class ThreadChangeBestAnswerApiTests(ThreadPatchApiTestCase):
 
 class ThreadUnmarkBestAnswerApiTests(ThreadPatchApiTestCase):
     def setUp(self):
-        super(ThreadUnmarkBestAnswerApiTests, self).setUp()
+        super().setUp()
 
         self.best_answer = testutils.reply_thread(self.thread)
         self.thread.set_best_answer(self.user, self.best_answer)

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

@@ -10,7 +10,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPollApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPollApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(self.category, poster=self.user)

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

@@ -10,7 +10,7 @@ from .test_thread_poll_api import ThreadPollApiTestCase
 
 class ThreadPollDeleteTests(ThreadPollApiTestCase):
     def setUp(self):
-        super(ThreadPollDeleteTests, self).setUp()
+        super().setUp()
 
         self.mock_poll()
 

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

@@ -10,7 +10,7 @@ from .test_thread_poll_api import ThreadPollApiTestCase
 
 class ThreadPollEditTests(ThreadPollApiTestCase):
     def setUp(self):
-        super(ThreadPollEditTests, self).setUp()
+        super().setUp()
 
         self.mock_poll()
 

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

@@ -14,7 +14,7 @@ UserModel = get_user_model()
 
 class ThreadGetVotesTests(ThreadPollApiTestCase):
     def setUp(self):
-        super(ThreadGetVotesTests, self).setUp()
+        super().setUp()
 
         self.mock_poll()
 
@@ -156,7 +156,7 @@ class ThreadGetVotesTests(ThreadPollApiTestCase):
 
 class ThreadPostVotesTests(ThreadPollApiTestCase):
     def setUp(self):
-        super(ThreadPostVotesTests, self).setUp()
+        super().setUp()
 
         self.mock_poll()
 

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

@@ -12,7 +12,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class PostBulkDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(PostBulkDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.posts = [
             testutils.reply_thread(self.thread, poster=self.user),

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import json
 from datetime import timedelta
 
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPostBulkPatchApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPostBulkPatchApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -11,7 +11,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class PostDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(PostDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.post = testutils.reply_thread(self.thread, poster=self.user)
 
@@ -176,7 +176,7 @@ class PostDeleteApiTests(ThreadsApiTestCase):
 
 class EventDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(EventDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.event = testutils.reply_thread(self.thread, poster=self.user, is_event=True)
 

+ 2 - 5
misago/threads/tests/test_thread_postedits_api.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.threads import testutils
@@ -10,7 +7,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadPostEditsApiTestCase(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadPostEditsApiTestCase, self).setUp()
+        super().setUp()
 
         self.post = testutils.reply_thread(self.thread, poster=self.user)
 
@@ -129,7 +126,7 @@ class ThreadPostGetEditTests(ThreadPostEditsApiTestCase):
 
 class ThreadPostPostEditTests(ThreadPostEditsApiTestCase):
     def setUp(self):
-        super(ThreadPostPostEditTests, self).setUp()
+        super().setUp()
         self.edits = self.mock_edit_record()
 
         self.override_acl({'can_edit_posts': 2})

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

@@ -8,7 +8,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadPostLikesApiTestCase(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadPostLikesApiTestCase, self).setUp()
+        super().setUp()
 
         self.post = testutils.reply_thread(self.thread, poster=self.user)
 

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import json
 
 from django.urls import reverse
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPostMergeApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPostMergeApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import json
 
 from django.urls import reverse
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPostMoveApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPostMoveApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

+ 2 - 5
misago/threads/tests/test_thread_postpatch_api.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import json
 from datetime import timedelta
 
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPostPatchApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPostPatchApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)
@@ -1137,7 +1134,7 @@ class PostLikeApiTests(ThreadPostPatchApiTestCase):
 
 class ThreadEventPatchApiTestCase(ThreadPostPatchApiTestCase):
     def setUp(self):
-        super(ThreadEventPatchApiTestCase, self).setUp()
+        super().setUp()
 
         self.event = testutils.reply_thread(self.thread, poster=self.user, is_event=True)
 

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

@@ -8,7 +8,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class PostReadApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(PostReadApiTests, self).setUp()
+        super().setUp()
 
         self.post = testutils.reply_thread(
             self.thread,

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import json
 
 from django.urls import reverse
@@ -16,7 +13,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadPostSplitApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadPostSplitApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.acl.testutils import override_acl
@@ -12,7 +9,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ReplyThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ReplyThreadTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.acl.testutils import override_acl
@@ -10,7 +7,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class StartThreadTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(StartThreadTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.api_link = reverse('misago:api:thread-list')

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

@@ -14,7 +14,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ThreadsApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadsApiTestCase, self).setUp()
+        super().setUp()
 
         threads_tree_id = trees_map.get_tree_id_for_root(THREADS_ROOT_NAME)
 
@@ -72,7 +72,7 @@ class ThreadsApiTestCase(AuthenticatedUserTestCase):
 
 class ThreadRetrieveApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadRetrieveApiTests, self).setUp()
+        super().setUp()
 
         self.tested_links = [
             self.api_link,
@@ -196,7 +196,7 @@ class ThreadRetrieveApiTests(ThreadsApiTestCase):
 
 class ThreadDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.last_thread = testutils.post_thread(category=self.category)
         self.api_link = self.last_thread.get_api_url()

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

@@ -15,7 +15,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadsBulkDeleteApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadsBulkDeleteApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:thread-list')
 

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

@@ -17,7 +17,7 @@ TEST_DOCUMENT_PATH = os.path.join(TESTFILES_DIR, 'document.pdf')
 
 class EditorApiTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(EditorApiTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
 
@@ -71,7 +71,7 @@ class EditorApiTestCase(AuthenticatedUserTestCase):
 
 class ThreadPostEditorApiTests(EditorApiTestCase):
     def setUp(self):
-        super(ThreadPostEditorApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:thread-editor')
 
@@ -257,7 +257,7 @@ class ThreadPostEditorApiTests(EditorApiTestCase):
 
 class ThreadReplyEditorApiTests(EditorApiTestCase):
     def setUp(self):
-        super(ThreadReplyEditorApiTests, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(category=self.category)
         self.api_link = reverse(
@@ -400,7 +400,7 @@ class ThreadReplyEditorApiTests(EditorApiTestCase):
 
 class EditReplyEditorApiTests(EditorApiTestCase):
     def setUp(self):
-        super(EditReplyEditorApiTests, self).setUp()
+        super().setUp()
 
         self.thread = testutils.post_thread(category=self.category)
         self.post = testutils.reply_thread(self.thread, poster=self.user)

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

@@ -16,7 +16,7 @@ from .test_threads_api import ThreadsApiTestCase
 
 class ThreadsMergeApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(ThreadsMergeApiTests, self).setUp()
+        super().setUp()
         self.api_link = reverse('misago:api:thread-merge')
 
         Category(
@@ -1065,11 +1065,11 @@ class ThreadsMergeApiTests(ThreadsApiTestCase):
                     ['0', "Delete all polls"],
                     [
                         str(other_poll.pk),
-                        u'{} ({})'.format(other_poll.question, other_poll.thread.title),
+                        '{} ({})'.format(other_poll.question, other_poll.thread.title),
                     ],
                     [
                         str(poll.pk),
-                        u'{} ({})'.format(poll.question, poll.thread.title),
+                        '{} ({})'.format(poll.question, poll.thread.title),
                     ],
                 ],
             }

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

@@ -12,14 +12,14 @@ class MockRequest(object):
 
 class ThreadsModerationTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadsModerationTests, self).setUp()
+        super().setUp()
 
         self.request = MockRequest(self.user)
         self.category = Category.objects.all_categories()[:1][0]
         self.thread = testutils.post_thread(self.category)
 
     def tearDown(self):
-        super(ThreadsModerationTests, self).tearDown()
+        super().tearDown()
 
     def reload_thread(self):
         self.thread = Thread.objects.get(pk=self.thread.pk)

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

@@ -31,7 +31,7 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
         Category E
           + Subcategory F
         """
-        super(ThreadsListTestCase, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:thread-list')
 
@@ -161,10 +161,10 @@ class ThreadsListTestCase(AuthenticatedUserTestCase):
         return categories_acl
 
     def assertContainsThread(self, response, thread):
-        self.assertContains(response, u' href="{}"'.format(thread.get_absolute_url()))
+        self.assertContains(response, ' href="{}"'.format(thread.get_absolute_url()))
 
     def assertNotContainsThread(self, response, thread):
-        self.assertNotContains(response, u' href="{}"'.format(thread.get_absolute_url()))
+        self.assertNotContains(response, ' href="{}"'.format(thread.get_absolute_url()))
 
 
 class ApiTests(ThreadsListTestCase):
@@ -1530,7 +1530,7 @@ class UnapprovedListTests(ThreadsListTestCase):
 
 class OwnerOnlyThreadsVisibilityTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(OwnerOnlyThreadsVisibilityTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
 

+ 8 - 9
misago/threads/tests/test_threadview.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from misago.acl.testutils import override_acl
 from misago.categories.models import Category
 from misago.conf import settings
@@ -18,7 +17,7 @@ class MockRequest(object):
 
 class ThreadViewTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ThreadViewTestCase, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.thread = testutils.post_thread(category=self.category)
@@ -528,7 +527,7 @@ class ThreadAnonViewTests(ThreadViewTestCase):
 class ThreadUnicodeSupportTests(ThreadViewTestCase):
     def test_category_name(self):
         """unicode in category name causes no showstopper"""
-        self.category.name = u'Łódź'
+        self.category.name = 'Łódź'
         self.category.slug = 'Lodz'
 
         self.category.save()
@@ -540,7 +539,7 @@ class ThreadUnicodeSupportTests(ThreadViewTestCase):
 
     def test_thread_title(self):
         """unicode in thread title causes no showstopper"""
-        self.thread.title = u'Łódź'
+        self.thread.title = 'Łódź'
         self.thread.slug = 'Lodz'
 
         self.thread.save()
@@ -552,8 +551,8 @@ class ThreadUnicodeSupportTests(ThreadViewTestCase):
 
     def test_post_content(self):
         """unicode in thread title causes no showstopper"""
-        self.thread.first_post.original = u'Łódź'
-        self.thread.first_post.parsed = u'<p>Łódź</p>'
+        self.thread.first_post.original = 'Łódź'
+        self.thread.first_post.parsed = '<p>Łódź</p>'
 
         update_post_checksum(self.thread.first_post)
 
@@ -566,9 +565,9 @@ class ThreadUnicodeSupportTests(ThreadViewTestCase):
 
     def test_user_rank(self):
         """unicode in user rank causes no showstopper"""
-        self.user.title = u'Łódź'
-        self.user.rank.name = u'Łódź'
-        self.user.rank.title = u'Łódź'
+        self.user.title = 'Łódź'
+        self.user.rank.name = 'Łódź'
+        self.user.rank.title = 'Łódź'
 
         self.user.rank.save()
         self.user.save()

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

@@ -1,5 +1,4 @@
 from django.test import TestCase
-from django.utils import six
 
 from misago.categories.models import Category
 from misago.threads.threadtypes.treesmap import TreesMap
@@ -82,7 +81,7 @@ class TreesMapTests(TestCase):
         except KeyError as e:
             self.assertIn(
                 "tree id has no type defined",
-                six.text_type(e), "invalid exception message as given"
+                str(e), "invalid exception message as given"
             )
 
     def test_get_tree_id_for_root(self):
@@ -101,5 +100,5 @@ class TreesMapTests(TestCase):
         except KeyError as e:
             self.assertIn(
                 '"hurr_durr" root has no tree defined',
-                six.text_type(e), "invalid exception message as given"
+                str(e), "invalid exception message as given"
             )

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.categories.models import Category
 from misago.threads import testutils

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

@@ -20,7 +20,7 @@ class AddCategoriesToItemsTests(MisagoTestCase):
           + Subcategory F
         """
 
-        super(AddCategoriesToItemsTests, self).setUp()
+        super().setUp()
 
         self.root = Category.objects.root_category()
 

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

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.urls import reverse
 
 from misago.categories.models import Category
@@ -9,7 +6,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class ValidatePostTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ValidatePostTests, self).setUp()
+        super().setUp()
 
         self.category = Category.objects.get(slug='first-category')
         self.api_link = reverse('misago:api:thread-list')

+ 3 - 3
misago/threads/utils.py

@@ -1,6 +1,6 @@
+from urllib.parse import urlparse
+
 from django.urls import resolve
-from django.utils import six
-from django.utils.six.moves.urllib.parse import urlparse
 
 from .models import PostLike
 
@@ -40,7 +40,7 @@ SUPPORTED_THREAD_ROUTES = {
 
 def get_thread_id_from_url(request, url):
     try:
-        clean_url = six.text_type(url).strip()
+        clean_url = str(url).strip()
         bits = urlparse(clean_url)
     except:
         return None

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

@@ -164,8 +164,7 @@ class ForumThreads(ViewModel):
 
 class PrivateThreads(ViewModel):
     def get_base_queryset(self, request, threads_categories, list_type):
-        queryset = super(PrivateThreads, self).get_base_queryset(
-            request, threads_categories, list_type)
+        queryset = super().get_base_queryset(request, threads_categories, list_type)
 
         # limit queryset to threads we are participant of
         participated_threads = request.user.threadparticipant_set.values('thread_id')

+ 1 - 1
misago/threads/views/admin/attachments.py

@@ -14,7 +14,7 @@ class AttachmentAdmin(generic.AdminBaseMixin):
     message_404 = _("Requested attachment could not be found.")
 
     def get_queryset(self):
-        qs = super(AttachmentAdmin, self).get_queryset()
+        qs = super().get_queryset()
         return qs.select_related('filetype', 'uploader', 'post', 'post__thread', 'post__category')
 
 

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

@@ -20,7 +20,7 @@ class AttachmentTypeAdmin(generic.AdminBaseMixin):
             target.roles.add(*roles)
 
     def handle_form(self, form, request, target):
-        super(AttachmentTypeAdmin, self).handle_form(form, request, target)
+        super().handle_form(form, request, target)
         form.save()
 
 
@@ -28,7 +28,7 @@ class AttachmentTypesList(AttachmentTypeAdmin, generic.ListView):
     ordering = (('name', None), )
 
     def get_queryset(self):
-        queryset = super(AttachmentTypesList, self).get_queryset()
+        queryset = super().get_queryset()
         return queryset.annotate(num_files=Count('attachment'))
 
 

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

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django.core.exceptions import PermissionDenied
 from django.http import Http404
 from django.shortcuts import get_object_or_404, redirect

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

@@ -73,7 +73,7 @@ class CategoryThreadsList(ForumThreadsList):
     template_name = 'misago/threadslist/category.html'
 
     def get_category(self, request, **kwargs):
-        category = super(CategoryThreadsList, self).get_category(request, **kwargs)
+        category = super().get_category(request, **kwargs)
         if not category.level:
             raise Http404()  # disallow root category access
         return category

+ 2 - 2
misago/users/api/userendpoints/editdetails.py

@@ -55,10 +55,10 @@ class DetailsForm(forms.Form):
         self.request = kwargs.pop('request')
         self.user = kwargs.pop('user')
 
-        super(DetailsForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         profilefields.add_fields_to_form(self.request, self.user, self)
 
     def clean(self):
-        data = super(DetailsForm, self).clean()
+        data = super().clean()
         return profilefields.clean_form(self.request, self.user, self, data)

+ 16 - 8
misago/users/avatars/gallery.py

@@ -1,6 +1,6 @@
 import random
+from pathlib import Path
 
-from path import Path
 from PIL import Image
 
 from django.core.files.base import ContentFile
@@ -48,23 +48,31 @@ def load_avatar_galleries():
     from misago.users.models import AvatarGallery
 
     galleries = []
-    for directory in Path(settings.MISAGO_AVATAR_GALLERY).dirs():
-        gallery_name = directory.name
+    for directory in Path(settings.MISAGO_AVATAR_GALLERY).iterdir():
+        if not directory.is_dir():
+            continue
 
-        images = directory.files('*.gif')
-        images += directory.files('*.jpg')
-        images += directory.files('*.jpeg')
-        images += directory.files('*.png')
+        name = directory.name
+        images = glob_gallery_images(directory)
 
         for image in images:
             with open(image, 'rb') as image_file:
                 galleries.append(
                     AvatarGallery.objects.
-                    create(gallery=gallery_name, image=ContentFile(image_file.read(), 'image'))
+                    create(gallery=name, image=ContentFile(image_file.read(), 'image'))
                 )
     return galleries
 
 
+def glob_gallery_images(directory):
+    images = []
+    images.extend(directory.glob('*.gif'))
+    images.extend(directory.glob('*.jpg'))
+    images.extend(directory.glob('*.jpeg'))
+    images.extend(directory.glob('*.png'))
+    return images
+
+
 def set_avatar(user, avatar):
     store.store_new_avatar(user, Image.open(avatar.image))
 

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

@@ -1,4 +1,5 @@
-from path import Path
+from pathlib import Path
+
 from PIL import Image
 
 from django.core.exceptions import ValidationError
@@ -62,7 +63,7 @@ def validate_uploaded_file(uploaded_file):
         try:
             temporary_file_path = Path(uploaded_file.temporary_file_path())
             if temporary_file_path.exists():
-                temporary_file_path.remove()
+                temporary_file_path.unlink()
         except Exception:
             pass
         raise e

+ 2 - 3
misago/users/credentialchange.py

@@ -6,7 +6,6 @@ Stores new e-mail and password in cache
 from hashlib import sha256
 
 from django.conf import settings
-from django.utils import six
 from django.utils.encoding import force_bytes
 
 
@@ -45,7 +44,7 @@ def read_new_credential(request, credential_type, link_token):
 def _make_change_token(user, token_type):
     seeds = (
         user.pk, user.email, user.password, user.last_login.replace(microsecond=0, tzinfo=None),
-        settings.SECRET_KEY, six.text_type(token_type)
+        settings.SECRET_KEY, str(token_type)
     )
 
-    return sha256(force_bytes('+'.join([six.text_type(s) for s in seeds]))).hexdigest()
+    return sha256(force_bytes('+'.join([str(s) for s in seeds]))).hexdigest()

+ 8 - 10
misago/users/datadownloads/dataarchive.py

@@ -1,11 +1,9 @@
-import io  # fixme: remove explicit io imports after going py3k-only
 import os
 import shutil
 
 from django.core.files import File
 from django.utils import timezone
 from django.utils.crypto import get_random_string
-from django.utils import six
 
 from misago.core.utils import slugify
 
@@ -78,15 +76,15 @@ class DataArchive(object):
         clean_filename = slugify(str(name))
         file_dir_path = self.make_final_path(date=date, directory=directory)
         file_path = os.path.join(file_dir_path, '{}.txt'.format(clean_filename))
-        with io.open(file_path, 'w', encoding='utf-8') as fp:
-            fp.write(six.text_type(value))
+        with open(file_path, 'w') as fp:
+            fp.write(str(value))
             return file_path
 
     def add_dict(self, name, value, date=None, directory=None):
         text_lines = []
         for key, value in value.items():
-            text_lines.append(u"{}: {}".format(key, value))
-        text = u'\n'.join(text_lines)
+            text_lines.append("{}: {}".format(key, value))
+        text = '\n'.join(text_lines)
         return self.add_text(name, text, date=date, directory=directory)
 
     def add_model_file(self, model_file, prefix=None, date=None, directory=None):
@@ -97,7 +95,7 @@ class DataArchive(object):
 
         filename = os.path.basename(model_file.name)
         if prefix:
-            prefixed_filename = u"{}-{}".format(prefix, filename)
+            prefixed_filename = "{}-{}".format(prefix, filename)
             clean_filename = trim_long_filename(prefixed_filename)
             target_path = os.path.join(target_dir_path, clean_filename)
         else:
@@ -121,13 +119,13 @@ class DataArchive(object):
             final_path = data_dir_path
             path_items = [date.strftime('%Y'), date.strftime('%m'), date.strftime('%d')]
             for path_item in path_items:
-                final_path = os.path.join(final_path, six.text_type(path_item))
+                final_path = os.path.join(final_path, str(path_item))
                 if not os.path.isdir(final_path):
                     os.mkdir(final_path)
             return final_path
 
         if directory:
-            final_path = os.path.join(data_dir_path, six.text_type(directory))
+            final_path = os.path.join(data_dir_path, str(directory))
             if not os.path.isdir(final_path):
                 os.mkdir(final_path)
             return final_path
@@ -153,4 +151,4 @@ def trim_long_filename(filename):
 
     name, extension = os.path.splitext(filename)
     name_len = FILENAME_MAX_LEN - len(extension)
-    return u'{}{}'.format(name[:name_len], extension)
+    return '{}{}'.format(name[:name_len], extension)

+ 4 - 4
misago/users/forms/admin.py

@@ -193,7 +193,7 @@ class EditUserForm(UserBaseForm):
     def __init__(self, *args, **kwargs):
         self.request = kwargs.pop('request')
 
-        super(EditUserForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         profilefields.add_fields_to_admin_form(self.request, self.instance, self)
 
@@ -227,7 +227,7 @@ class EditUserForm(UserBaseForm):
         return data
 
     def clean(self):
-        data = super(EditUserForm, self).clean()
+        data = super().clean()
         return profilefields.clean_form(self.request, self.instance, self, data)
 
 
@@ -493,7 +493,7 @@ class BanUsersForm(forms.Form):
     def __init__(self, *args, **kwargs):
         users = kwargs.pop('users')
 
-        super(BanUsersForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
         self.fields['ban_type'].choices = [
             ('usernames', _('Usernames')),
@@ -672,7 +672,7 @@ class RequestDataDownloadsForm(forms.Form):
         return user_identifiers
 
     def clean(self):
-        data = super(RequestDataDownloadsForm, self).clean()
+        data = super().clean()
 
         if data.get('user_identifiers'):
             username_match = Q(slug__in=data['user_identifiers'])

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

@@ -91,7 +91,7 @@ class AdminAuthenticationForm(AuthenticationForm):
             'not_staff': _("Your account does not have admin privileges."),
         })
 
-        super(AdminAuthenticationForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def confirm_login_allowed(self, user):
         if not user.is_staff:
@@ -102,7 +102,7 @@ class GetUserForm(MisagoAuthMixin, forms.Form):
     email = forms.CharField()
 
     def clean(self):
-        data = super(GetUserForm, self).clean()
+        data = super().clean()
 
         email = data.get('email')
         if not email or len(email) > 250:

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

@@ -21,7 +21,7 @@ class BaseRegisterForm(forms.Form):
     def __init__(self, *args, **kwargs):
         self.agreements = kwargs.pop('agreements')
         self.request = kwargs.pop('request')
-        super(BaseRegisterForm, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def clean_username(self):
         data = self.cleaned_data['username']
@@ -62,7 +62,7 @@ class BaseRegisterForm(forms.Form):
 
 class SocialAuthRegisterForm(BaseRegisterForm):
     def clean(self):
-        cleaned_data = super(SocialAuthRegisterForm, self).clean()
+        cleaned_data = super().clean()
 
         self.clean_agreements(cleaned_data)
         self.raise_if_ip_banned()
@@ -89,7 +89,7 @@ class RegisterForm(BaseRegisterForm):
             )
 
     def clean(self):
-        cleaned_data = super(RegisterForm, self).clean()
+        cleaned_data = super().clean()
 
         self.clean_agreements(cleaned_data)
         self.raise_if_ip_banned()

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

@@ -11,7 +11,6 @@ from django.core.exceptions import ValidationError
 from django.core.management.base import BaseCommand
 from django.db import DEFAULT_DB_ALIAS, IntegrityError
 from django.utils.encoding import force_str
-from django.utils.six.moves import input
 
 from misago.users.validators import validate_email, validate_username
 
@@ -70,7 +69,7 @@ class Command(BaseCommand):
 
     def execute(self, *args, **options):
         self.stdin = options.get('stdin', sys.stdin)  # Used for testing
-        return super(Command, self).execute(*args, **options)
+        return super().execute(*args, **options)
 
     def handle(self, *args, **options):
         username = options.get('username')
@@ -85,7 +84,7 @@ class Command(BaseCommand):
                 username = username.strip()
                 validate_username(username)
             except ValidationError as e:
-                self.stderr.write(u'\n'.join(e.messages))
+                self.stderr.write('\n'.join(e.messages))
                 username = None
 
         if email is not None:
@@ -93,7 +92,7 @@ class Command(BaseCommand):
                 email = email.strip()
                 validate_email(email)
             except ValidationError as e:
-                self.stderr.write(u'\n'.join(e.messages))
+                self.stderr.write('\n'.join(e.messages))
                 email = None
 
         if password is not None:
@@ -120,7 +119,7 @@ class Command(BaseCommand):
                         validate_username(raw_value)
                         username = raw_value
                     except ValidationError as e:
-                        self.stderr.write(u'\n'.join(e.messages))
+                        self.stderr.write('\n'.join(e.messages))
 
                 while not email:
                     try:
@@ -128,7 +127,7 @@ class Command(BaseCommand):
                         validate_email(raw_value)
                         email = raw_value
                     except ValidationError as e:
-                        self.stderr.write(u'\n'.join(e.messages))
+                        self.stderr.write('\n'.join(e.messages))
 
                 while not password:
                     raw_value = getpass("Enter password: ")
@@ -146,7 +145,7 @@ class Command(BaseCommand):
                             raw_value, user=UserModel(username=username, email=email)
                         )
                     except ValidationError as e:
-                        self.stderr.write(u'\n'.join(e.messages))
+                        self.stderr.write('\n'.join(e.messages))
                         response = input('Bypass password validation and create user anyway? [y/N]: ')
                         if response.lower() != 'y':
                             continue

+ 0 - 2
misago/users/management/commands/deleteinactiveusers.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from datetime import timedelta
 
 from django.contrib.auth import get_user_model

+ 0 - 2
misago/users/management/commands/deletemarkedusers.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.core.management.base import CommandError, BaseCommand
 

+ 0 - 2
misago/users/management/commands/deleteprofilefield.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.core.management.base import CommandError, BaseCommand
 

+ 0 - 2
misago/users/management/commands/listusedprofilefields.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 from django.contrib.auth import get_user_model
 from django.core.management.base import BaseCommand
 

+ 0 - 3
misago/users/migrations/0001_initial.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 import django.db.models.deletion
 import django.utils.timezone
 from django.conf import settings

+ 0 - 3
misago/users/migrations/0002_users_settings.py

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

+ 0 - 3
misago/users/migrations/0003_bans_version_tracker.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 from misago.core.migrationutils import cachebuster_register_cache

+ 0 - 3
misago/users/migrations/0004_default_ranks.py

@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
 from django.db import migrations
 from django.utils.translation import ugettext
 

+ 0 - 3
misago/users/migrations/0005_dj_19_update.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.9.7 on 2016-07-17 02:05
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 import misago.users.models.user

+ 0 - 3
misago/users/migrations/0006_update_settings.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.10.5 on 2017-02-05 14:34
-from __future__ import unicode_literals
-
 from django.db import migrations
 
 from misago.conf.migrationutils import delete_settings_cache, migrate_settings_group

+ 0 - 3
misago/users/migrations/0007_auto_20170219_1639.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.10.5 on 2017-02-19 16:39
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/users/migrations/0008_ban_registration_only.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.10.5 on 2017-03-04 22:06
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/users/migrations/0009_redo_partial_indexes.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.1 on 2017-05-26 21:56
-from __future__ import unicode_literals
-
 from django.db import migrations
 import misago.core.pgutils
 

+ 0 - 3
misago/users/migrations/0010_user_profile_fields.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.1 on 2017-06-03 22:15
-from __future__ import unicode_literals
-
 import django.contrib.postgres.fields
 from django.contrib.postgres.operations import HStoreExtension
 from django.db import migrations

+ 0 - 3
misago/users/migrations/0011_auto_20180331_2208.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.11 on 2018-03-31 22:08
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 import misago.core.pgutils
 

+ 0 - 3
misago/users/migrations/0012_audittrail.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.13 on 2018-06-03 18:46
-from __future__ import unicode_literals
-
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion

+ 0 - 3
misago/users/migrations/0013_auto_20180609_1523.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.13 on 2018-06-09 15:23
-from __future__ import unicode_literals
-
 from django.db import migrations, models
 
 

+ 0 - 3
misago/users/migrations/0014_datadownload.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.13 on 2018-06-24 00:13
-from __future__ import unicode_literals
-
 from django.conf import settings
 from django.db import migrations, models
 import django.db.models.deletion

+ 0 - 3
misago/users/migrations/0015_user_agreements.py

@@ -1,7 +1,4 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.11.15 on 2018-08-16 17:29
-from __future__ import unicode_literals
-
 import django.contrib.postgres.fields
 from django.db import migrations, models
 

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

@@ -90,7 +90,7 @@ class Ban(models.Model):
         self.banned_value = self.banned_value.lower()
         self.is_checked = not self.is_expired
 
-        return super(Ban, self).save(*args, **kwargs)
+        return super().save(*args, **kwargs)
 
     def get_serialized_message(self):
         from misago.users.serializers import BanMessageSerializer
@@ -138,7 +138,7 @@ class BanCache(models.Model):
 
     def save(self, *args, **kwargs):
         try:
-            super(BanCache, self).save(*args, **kwargs)
+            super().save(*args, **kwargs)
         except IntegrityError:
             pass  # first come is first serve with ban cache
 

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

@@ -50,4 +50,4 @@ class DataDownload(models.Model):
     def delete(self, *args, **kwargs):
         if self.file:
             self.file.delete(save=False)
-        super(DataDownload, self).delete(*args, **kwargs)
+        super().delete(*args, **kwargs)

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

@@ -1,6 +1,5 @@
 from django.db import models, transaction
 from django.urls import reverse
-from django.utils.encoding import python_2_unicode_compatible
 
 from misago.acl import version as acl_version
 from misago.core.utils import slugify
@@ -17,7 +16,6 @@ class RankManager(models.Manager):
             rank.save(update_fields=['is_default'])
 
 
-@python_2_unicode_compatible
 class Rank(models.Model):
     name = models.CharField(max_length=255)
     slug = models.CharField(unique=True, max_length=255)
@@ -42,11 +40,11 @@ class Rank(models.Model):
             self.set_order()
         else:
             acl_version.invalidate()
-        return super(Rank, self).save(*args, **kwargs)
+        return super().save(*args, **kwargs)
 
     def delete(self, *args, **kwargs):
         acl_version.invalidate()
-        return super(Rank, self).delete(*args, **kwargs)
+        return super().delete(*args, **kwargs)
 
     def get_absolute_url(self):
         return reverse('misago:users-rank', kwargs={'slug': self.slug})

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

@@ -305,7 +305,7 @@ class User(AbstractBaseUser, PermissionsMixin):
 
         avatars.delete_avatar(self)
 
-        return super(User, self).delete(*args, **kwargs)
+        return super().delete(*args, **kwargs)
 
     def delete_content(self):
         from misago.users.signals import delete_user_content
@@ -479,7 +479,7 @@ class Online(models.Model):
 
     def save(self, *args, **kwargs):
         try:
-            super(Online, self).save(*args, **kwargs)
+            super().save(*args, **kwargs)
         except IntegrityError:
             pass  # first come is first serve in online tracker
 

+ 0 - 2
misago/users/profilefields/__init__.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import copy
 import logging
 

+ 3 - 4
misago/users/profilefields/basefields.py

@@ -1,7 +1,6 @@
 from django import forms
 from django.db.models import Q
 from django.utils import html
-from django.utils.six import text_type
 
 from misago.core.utils import format_plaintext_for_html
 
@@ -67,7 +66,7 @@ class ProfileField(object):
 
         data.update({
             'fieldname': self.fieldname,
-            'name': text_type(self.get_label(user)),
+            'name': str(self.get_label(user)),
         })
 
         return data
@@ -124,7 +123,7 @@ class ChoiceProfileField(ProfileField):
         for key, name in self.get_choices():
             if key == value:
                 return {
-                    'text': text_type(name),
+                    'text': str(name),
                 }
         return None
 
@@ -135,7 +134,7 @@ class ChoiceProfileField(ProfileField):
         })
 
         for key, choice in self.get_choices():
-            if key and criteria.lower() in text_type(choice).lower():
+            if key and criteria.lower() in str(choice).lower():
                 q_obj = q_obj | Q(**{
                     'profile_fields__{}'.format(self.fieldname): key
                 })

+ 0 - 2
misago/users/profilefields/default.py

@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import re
 
 from django.forms import ValidationError

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

@@ -181,7 +181,7 @@ def create_user_with_form(strategy, details, backend, user=None, *args, **kwargs
 
     if request.method == 'POST':
         try:
-            request_data = json.loads(request.body.decode('utf-8'))
+            request_data = json.loads(request.body)
         except (TypeError, ValueError):
             request_data = request.POST.copy()
             

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

@@ -17,7 +17,7 @@ UserModel = get_user_model()
 
 class TestActivePostersRanking(AuthenticatedUserTestCase):
     def setUp(self):
-        super(TestActivePostersRanking, self).setUp()
+        super().setUp()
 
         cache.clear()
         threadstore.clear()
@@ -25,7 +25,7 @@ class TestActivePostersRanking(AuthenticatedUserTestCase):
         self.category = Category.objects.get(slug='first-category')
 
     def tearDown(self):
-        super(TestActivePostersRanking, self).tearDown()
+        super().tearDown()
 
         cache.clear()
         threadstore.clear()

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

@@ -23,7 +23,7 @@ class MockRequest(object):
 
 class CreateAuditTrailTests(UserTestCase):
     def setUp(self):
-        super(CreateAuditTrailTests, self).setUp()
+        super().setUp()
 
         self.obj = UserModel.objects.create_user('BobBoberson', 'bob@example.com')
 
@@ -95,7 +95,7 @@ class CreateAuditTrailTests(UserTestCase):
 
 class CreateUserAuditTrailTests(UserTestCase):
     def setUp(self):
-        super(CreateUserAuditTrailTests, self).setUp()
+        super().setUp()
 
         self.obj = UserModel.objects.create_user('BobBoberson', 'bob@example.com')
 
@@ -161,7 +161,7 @@ class CreateUserAuditTrailTests(UserTestCase):
 
 class RemoveOldAuditTrailsTest(UserTestCase):
     def setUp(self):
-        super(RemoveOldAuditTrailsTest, self).setUp()
+        super().setUp()
 
         self.obj = UserModel.objects.create_user('BobBoberson', 'bob@example.com')
         

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

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.test import TestCase
 from django.urls import reverse
 
@@ -68,7 +67,7 @@ class AuthViewsTests(TestCase):
         response = self.client.post(
             reverse('misago:login'),
             data={
-                'redirect_to': u'łelcome!',
+                'redirect_to': 'łelcome!',
             },
         )
 

+ 5 - 4
misago/users/tests/test_avatars.py

@@ -1,4 +1,5 @@
-from path import Path
+from pathlib import Path
+
 from PIL import Image
 
 from django.contrib.auth import get_user_model
@@ -47,7 +48,7 @@ class AvatarsStoreTests(TestCase):
         for old_avatar in avatars_dict.values():
             avatar_path = Path(old_avatar.image.path)
             self.assertFalse(avatar_path.exists())
-            self.assertFalse(avatar_path.isfile())
+            self.assertFalse(avatar_path.is_file())
 
             with self.assertRaises(Avatar.DoesNotExist):
                 Avatar.objects.get(pk=old_avatar.pk)
@@ -76,7 +77,7 @@ class AvatarsStoreTests(TestCase):
         for removed_avatar in new_avatars_dict.values():
             avatar_path = Path(removed_avatar.image.path)
             self.assertFalse(avatar_path.exists())
-            self.assertFalse(avatar_path.isfile())
+            self.assertFalse(avatar_path.is_file())
 
             with self.assertRaises(Avatar.DoesNotExist):
                 Avatar.objects.get(pk=removed_avatar.pk)
@@ -106,7 +107,7 @@ class AvatarSetterTests(TestCase):
         for avatar in user.avatar_set.all():
             avatar_path = Path(avatar.image.path)
             self.assertTrue(avatar_path.exists())
-            self.assertTrue(avatar_path.isfile())
+            self.assertTrue(avatar_path.is_file())
 
             avatars_dict[avatar.size] = avatar
 

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

@@ -1,4 +1,3 @@
-#-*- coding: utf-8 -*-
 from django.test import TestCase
 
 from misago.users.models import Ban

+ 5 - 6
misago/users/tests/test_bio_profilefield.py

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
-from django.utils import six
 
 from misago.admin.testutils import AdminTestCase
 
@@ -10,7 +9,7 @@ UserModel = get_user_model()
 
 class BioProfileFieldTests(AdminTestCase):
     def setUp(self):
-        super(BioProfileFieldTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse(
             'misago:admin:users:accounts:edit',
@@ -36,8 +35,8 @@ class BioProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': '',
                 'signature': '',
@@ -61,8 +60,8 @@ class BioProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'bio': 'Edited field!',
                 'new_password': '',

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

@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 
 UserModel = get_user_model()

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

@@ -60,7 +60,7 @@ class ExpireUserDataDownloadTests(AuthenticatedUserTestCase):
 
 class PrepareUserDataDownload(AuthenticatedUserTestCase):
     def setUp(self):
-        super(PrepareUserDataDownload, self).setUp()
+        super().setUp()
         self.download = request_user_data_download(self.user)
 
     def assert_download_is_valid(self):

+ 13 - 15
misago/users/tests/test_datadownloads_dataarchive.py

@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-import io  # fixme: remove explicit io imports after going py3k-only
 import os
 from collections import OrderedDict
 
@@ -56,14 +54,14 @@ class DataArchiveTests(AuthenticatedUserTestCase):
     def test_add_text_str(self):
         """add_dict method creates text file with string"""
         with DataArchive(self.user, DATA_DOWNLOADS_WORKING_DIR) as archive:
-            data_to_write = u"Hello, łorld!"
+            data_to_write = "Hello, łorld!"
             file_path = archive.add_text('testfile', data_to_write)
             self.assertTrue(os.path.isfile(file_path))
 
             valid_output_path = os.path.join(archive.data_dir_path, 'testfile.txt')
             self.assertEqual(file_path, valid_output_path)
 
-            with io.open(file_path, 'r', encoding="utf-8") as fp:
+            with open(file_path, 'r') as fp:
                 saved_data = fp.read().strip()
                 self.assertEqual(saved_data, data_to_write)
 
@@ -77,44 +75,44 @@ class DataArchiveTests(AuthenticatedUserTestCase):
             valid_output_path = os.path.join(archive.data_dir_path, 'testfile.txt')
             self.assertEqual(file_path, valid_output_path)
 
-            with io.open(file_path, 'r', encoding="utf-8") as fp:
+            with open(file_path, 'r') as fp:
                 saved_data = fp.read().strip()
                 self.assertEqual(saved_data, str(data_to_write))
 
     def test_add_dict(self):
         """add_dict method creates text file from dict"""
         with DataArchive(self.user, DATA_DOWNLOADS_WORKING_DIR) as archive:
-            data_to_write = {'first': u"łorld!", 'second': u"łup!"}
+            data_to_write = {'first': "łorld!", 'second': "łup!"}
             file_path = archive.add_dict('testfile', data_to_write)
             self.assertTrue(os.path.isfile(file_path))
 
             valid_output_path = os.path.join(archive.data_dir_path, 'testfile.txt')
             self.assertEqual(file_path, valid_output_path)
 
-            with io.open(file_path, 'r', encoding="utf-8") as fp:
+            with open(file_path, 'r') as fp:
                 saved_data = fp.read().strip()
                 # order of dict items in py<3.6 is non-deterministic
                 # making testing for exact match a mistake
-                self.assertIn(u"first: łorld!", saved_data)
-                self.assertIn(u"second: łup!", saved_data)
+                self.assertIn("first: łorld!", saved_data)
+                self.assertIn("second: łup!", saved_data)
 
     def test_add_dict_ordered(self):
         """add_dict method creates text file form ordered dict"""
         with DataArchive(self.user, DATA_DOWNLOADS_WORKING_DIR) as archive:
-            data_to_write = OrderedDict((('first', u"łorld!"), ('second', u"łup!")))
+            data_to_write = OrderedDict((('first', "łorld!"), ('second', "łup!")))
             file_path = archive.add_dict('testfile', data_to_write)
             self.assertTrue(os.path.isfile(file_path))
 
             valid_output_path = os.path.join(archive.data_dir_path, 'testfile.txt')
             self.assertEqual(file_path, valid_output_path)
 
-            with io.open(file_path, 'r', encoding="utf-8") as fp:
+            with open(file_path, 'r') as fp:
                 saved_data = fp.read().strip()
-                self.assertEqual(saved_data, u"first: łorld!\nsecond: łup!")
+                self.assertEqual(saved_data, "first: łorld!\nsecond: łup!")
 
     def test_add_model_file(self):
         """add_model_file method adds model file"""
-        with io.open(TEST_AVATAR_PATH, 'rb') as avatar:
+        with open(TEST_AVATAR_PATH, 'rb') as avatar:
             self.user.avatar_tmp = File(avatar)
             self.user.save()
 
@@ -136,7 +134,7 @@ class DataArchiveTests(AuthenticatedUserTestCase):
 
     def test_add_model_file_prefixed(self):
         """add_model_file method adds model file with prefix"""
-        with io.open(TEST_AVATAR_PATH, 'rb') as avatar:
+        with open(TEST_AVATAR_PATH, 'rb') as avatar:
             self.user.avatar_tmp = File(avatar)
             self.user.save()
 
@@ -206,7 +204,7 @@ class DataArchiveTests(AuthenticatedUserTestCase):
         """get_file returns django file"""
         django_file = None
         
-        with io.open(TEST_AVATAR_PATH, 'rb') as avatar:
+        with open(TEST_AVATAR_PATH, 'rb') as avatar:
             self.user.avatar_tmp = File(avatar)
             self.user.save()
 

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

@@ -1,10 +1,10 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase, override_settings
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.users.management.commands import deleteinactiveusers
 

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

@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase, override_settings
-from django.utils.six import StringIO
 
 from misago.users.management.commands import deletemarkedusers
 

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

@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.users.management.commands import deleteprofilefield
 

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

@@ -11,7 +11,7 @@ from misago.users.djangoadmin import UserAdminModel
 @override_settings(ROOT_URLCONF='misago.core.testproject.urls')
 class TestDjangoAdminUserForm(AdminTestCase):
     def setUp(self):
-        super(TestDjangoAdminUserForm, self).setUp()
+        super().setUp()
         self.test_user = get_user_model().objects.create_user(
             username='Bob',
             email='bob@test.com',

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

@@ -1,9 +1,9 @@
 import os
 from datetime import timedelta
+from io import StringIO
 
 from django.core.files import File
 from django.core.management import call_command
-from django.utils.six import StringIO
 
 from misago.users.datadownloads import request_user_data_download
 from misago.users.management.commands import expireuserdatadownloads

+ 7 - 8
misago/users/tests/test_gender_profilefield.py

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
-from django.utils import six
 
 from misago.admin.testutils import AdminTestCase
 
@@ -10,7 +9,7 @@ UserModel = get_user_model()
 
 class GenderProfileFieldTests(AdminTestCase):
     def setUp(self):
-        super(GenderProfileFieldTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse(
             'misago:admin:users:accounts:edit',
@@ -36,8 +35,8 @@ class GenderProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': '',
                 'signature': '',
@@ -61,8 +60,8 @@ class GenderProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'gender': 'attackcopter',
                 'new_password': '',
@@ -85,8 +84,8 @@ class GenderProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'gender': 'female',
                 'new_password': '',

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

@@ -1,10 +1,10 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.users import bans
 from misago.users.management.commands import invalidatebans

+ 3 - 4
misago/users/tests/test_joinip_profilefield.py

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
-from django.utils import six
 
 from misago.admin.testutils import AdminTestCase
 from misago.acl.testutils import override_acl
@@ -11,7 +10,7 @@ UserModel = get_user_model()
 
 class JoinIpProfileFieldTests(AdminTestCase):
     def setUp(self):
-        super(JoinIpProfileFieldTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse(
             'misago:admin:users:accounts:edit',
@@ -33,8 +32,8 @@ class JoinIpProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'join_ip': '127.0.0.1',
                 'new_password': '',

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

@@ -14,7 +14,7 @@ UserModel = get_user_model()
 
 class UsersListTestCase(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UsersListTestCase, self).setUp()
+        super().setUp()
         override_acl(self.user, {
             'can_browse_users_list': 1,
         })

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

@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.users.management.commands import listusedprofilefields
 

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.users.management.commands import loadavatargallery
 from misago.users.models import AvatarGallery

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

@@ -10,7 +10,7 @@ UserModel = get_user_model()
 
 class GetUserStatusTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(GetUserStatusTests, self).setUp()
+        super().setUp()
         self.other_user = UserModel.objects.create_user('Tyrael', 't123@test.com', 'pass123')
 
     def test_user_hiding_presence(self):

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

@@ -25,7 +25,7 @@ class OptionsViewsTests(AuthenticatedUserTestCase):
 
 class ConfirmChangeEmailTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ConfirmChangeEmailTests, self).setUp()
+        super().setUp()
         link = '/api/users/%s/change-email/' % self.user.pk
 
         response = self.client.post(
@@ -64,7 +64,7 @@ class ConfirmChangeEmailTests(AuthenticatedUserTestCase):
 
 class ConfirmChangePasswordTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(ConfirmChangePasswordTests, self).setUp()
+        super().setUp()
         link = '/api/users/%s/change-password/' % self.user.pk
 
         response = self.client.post(

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

@@ -1,7 +1,8 @@
+from io import StringIO
+
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase
-from django.utils.six import StringIO
 
 from misago.users.management.commands import populateonlinetracker
 from misago.users.models import Online

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

@@ -1,6 +1,7 @@
+from io import StringIO
+
 from django.core import mail
 from django.core.management import call_command
-from django.utils.six import StringIO
 
 from misago.conf import settings
 from misago.users.datadownloads import request_user_data_download

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

@@ -13,7 +13,7 @@ UserModel = get_user_model()
 
 class UserProfileViewsTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserProfileViewsTests, self).setUp()
+        super().setUp()
         self.link_kwargs = {'slug': self.user.slug, 'pk': self.user.pk}
 
         self.category = Category.objects.get(slug='first-category')

+ 3 - 4
misago/users/tests/test_profilefields.py

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.test import TestCase
-from django.utils.six import text_type
 
 from misago.users.profilefields import ProfileFields
 
@@ -46,7 +45,7 @@ class ProfileFieldsLoadTests(TestCase):
         try:
             profilefields.load()
         except ValueError as e:
-            error = text_type(e)
+            error = str(e)
 
             self.assertIn('misago.users.tests.testfiles.profilefields.NofieldnameField', error)
             self.assertIn('profile field has to specify fieldname attribute', error)
@@ -74,7 +73,7 @@ class ProfileFieldsLoadTests(TestCase):
         try:
             profilefields.load()
         except ValueError as e:
-            error = text_type(e)
+            error = str(e)
 
             self.assertIn('misago.users.profilefields.default.TwitterHandleField', error)
             self.assertIn('profile field has been specified twice', error)
@@ -102,7 +101,7 @@ class ProfileFieldsLoadTests(TestCase):
         try:
             profilefields.load()
         except ValueError as e:
-            error = text_type(e)
+            error = str(e)
 
             self.assertIn('misago.users.tests.testfiles.profilefields.FieldnameField', error)
             self.assertIn('misago.users.tests.testfiles.profilefields.RepeatedFieldnameField', error)

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

@@ -1,10 +1,10 @@
 from datetime import timedelta
+from io import StringIO
 
 from django.contrib.auth import get_user_model
 from django.core.management import call_command
 from django.test import TestCase, override_settings
 from django.utils import timezone
-from django.utils.six import StringIO
 
 from misago.users.management.commands import removeoldips
 

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

@@ -10,7 +10,7 @@ UserModel = get_user_model()
 
 class SearchApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(SearchApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:search')
 
@@ -138,7 +138,7 @@ class SearchApiTests(AuthenticatedUserTestCase):
 
 class SearchProviderApiTests(SearchApiTests):
     def setUp(self):
-        super(SearchProviderApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse(
             'misago:api:search', kwargs={

+ 10 - 11
misago/users/tests/test_social_pipeline.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 import json
 
 from django.contrib.auth import get_user_model
@@ -230,12 +229,12 @@ class CreateUser(PipelineTestCase):
 
 class CreateUserWithFormTests(PipelineTestCase):
     def setUp(self):
-        super(CreateUserWithFormTests, self).setUp()
+        super().setUp()
 
         Agreement.objects.invalidate_cache()
 
     def tearDown(self):
-        super(CreateUserWithFormTests, self).tearDown()
+        super().tearDown()
         
         Agreement.objects.invalidate_cache()
 
@@ -580,14 +579,14 @@ class GetUsernameTests(PipelineTestCase):
 
     def test_normalize_username(self):
         """pipeline step normalizes username"""
-        result = get_username(None, {'username': u'Błop Błoperson'}, None)
+        result = get_username(None, {'username': 'Błop Błoperson'}, None)
         self.assertEqual(result, {'clean_username': 'BlopBloperson'})
 
     def test_resolve_to_first_name(self):
         """pipeline attempts to use first name because username is taken"""
         details = {
             'username': self.user.username,
-            'first_name': u'Błob',
+            'first_name': 'Błob',
         }
         result = get_username(None, details, None)
         self.assertEqual(result, {'clean_username': 'Blob'})
@@ -596,7 +595,7 @@ class GetUsernameTests(PipelineTestCase):
         """pipeline will not fallback to last name because username is taken"""
         details = {
             'username': self.user.username,
-            'last_name': u'Błob',
+            'last_name': 'Błob',
         }
         result = get_username(None, details, None)
         self.assertIsNone(result)
@@ -605,7 +604,7 @@ class GetUsernameTests(PipelineTestCase):
         """pipeline will construct username from first name and first char of surname"""
         details = {
             'first_name': self.user.username,
-            'last_name': u'Błob',
+            'last_name': 'Błob',
         }
         result = get_username(None, details, None)
         self.assertEqual(result, {'clean_username': self.user.username + 'B'})
@@ -615,7 +614,7 @@ class GetUsernameTests(PipelineTestCase):
         Ban.objects.create(banned_value='*Admin*', check_type=Ban.USERNAME)
         details = {
             'username': 'Misago Admin',
-            'first_name': u'Błob',
+            'first_name': 'Błob',
         }
         result = get_username(None, details, None)
         self.assertEqual(result, {'clean_username': 'Blob'})
@@ -625,7 +624,7 @@ class GetUsernameTests(PipelineTestCase):
         Ban.objects.create(banned_value='*Admin*', check_type=Ban.USERNAME)
         details = {
             'username': 'Misago Admin',
-            'full_name': u'Błob Błopo',
+            'full_name': 'Błob Błopo',
         }
         result = get_username(None, details, None)
         self.assertEqual(result, {'clean_username': 'BlobBlopo'})
@@ -633,7 +632,7 @@ class GetUsernameTests(PipelineTestCase):
     def test_resolve_to_cut_name(self):
         """pipeline will resolve cut too long name on second pass"""
         details = {
-            'username': u'Abrakadabrapokuskonstantynopolitańczykowianeczkatrzy',
+            'username': 'Abrakadabrapokuskonstantynopolitańczykowianeczkatrzy',
         }
         result = get_username(None, details, None)
         self.assertEqual(result, {'clean_username': 'Abrakadabrapok'})
@@ -641,7 +640,7 @@ class GetUsernameTests(PipelineTestCase):
 
 class RequireActivationTests(PipelineTestCase):
     def setUp(self):
-        super(RequireActivationTests, self).setUp()
+        super().setUp()
 
         self.user.requires_activation = UserModel.ACTIVATION_ADMIN
         self.user.save()

+ 7 - 8
misago/users/tests/test_twitter_profilefield.py

@@ -1,6 +1,5 @@
 from django.contrib.auth import get_user_model
 from django.urls import reverse
-from django.utils import six
 
 from misago.admin.testutils import AdminTestCase
 
@@ -10,7 +9,7 @@ UserModel = get_user_model()
 
 class TwitterProfileFieldTests(AdminTestCase):
     def setUp(self):
-        super(TwitterProfileFieldTests, self).setUp()
+        super().setUp()
 
         self.test_link = reverse(
             'misago:admin:users:accounts:edit',
@@ -36,8 +35,8 @@ class TwitterProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': '',
                 'signature': '',
@@ -61,8 +60,8 @@ class TwitterProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'twitter': 'lorem!ipsum',
                 'new_password': '',
@@ -85,8 +84,8 @@ class TwitterProfileFieldTests(AdminTestCase):
             self.test_link,
             data={
                 'username': 'Edited',
-                'rank': six.text_type(self.user.rank_id),
-                'roles': six.text_type(self.user.roles.all()[0].pk),
+                'rank': str(self.user.rank_id),
+                'roles': str(self.user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'twitter': 'lorem_ipsum',
                 'new_password': '',

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

@@ -1,7 +1,6 @@
 import json
 import os
-
-from path import Path
+from pathlib import Path
 
 from django.contrib.auth import get_user_model
 
@@ -22,7 +21,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
     """tests for user avatar RPC (/api/users/1/avatar/)"""
 
     def setUp(self):
-        super(UserAvatarTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/avatar/' % self.user.pk
         self.client.post(self.link, data={'avatar': 'generated'})
 
@@ -142,7 +141,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
 
         avatar = Path(self.get_current_user().avatar_tmp.path)
         self.assertTrue(avatar.exists())
-        self.assertTrue(avatar.isfile())
+        self.assertTrue(avatar.is_file())
 
         response = self.client.post(
             self.link,
@@ -168,7 +167,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
 
         avatar = Path(self.get_current_user().avatar_src.path)
         self.assertTrue(avatar.exists())
-        self.assertTrue(avatar.isfile())
+        self.assertTrue(avatar.is_file())
 
         response = self.client.post(
             self.link,
@@ -210,7 +209,7 @@ class UserAvatarTests(AuthenticatedUserTestCase):
 
         avatar = Path(self.get_current_user().avatar_src.path)
         self.assertFalse(avatar.exists())
-        self.assertFalse(avatar.isfile())
+        self.assertFalse(avatar.is_file())
 
     def test_gallery_set_empty_gallery(self):
         """gallery handles set avatar on empty gallery"""
@@ -297,7 +296,7 @@ class UserAvatarModerationTests(AuthenticatedUserTestCase):
     """tests for moderate user avatar RPC (/api/users/1/moderate-avatar/)"""
 
     def setUp(self):
-        super(UserAvatarModerationTests, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 

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

@@ -12,7 +12,7 @@ class UserChangeEmailTests(AuthenticatedUserTestCase):
     """tests for user change email RPC (/api/users/1/change-email/)"""
 
     def setUp(self):
-        super(UserChangeEmailTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/change-email/' % self.user.pk
 
     def test_unsupported_methods(self):

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

@@ -8,7 +8,7 @@ class UserChangePasswordTests(AuthenticatedUserTestCase):
     """tests for user change password RPC (/api/users/1/change-password/)"""
 
     def setUp(self):
-        super(UserChangePasswordTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/change-password/' % self.user.pk
 
     def test_unsupported_methods(self):

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

@@ -16,7 +16,7 @@ class UserCreateTests(UserTestCase):
     """tests for new user registration (POST to /api/users/)"""
 
     def setUp(self):
-        super(UserCreateTests, self).setUp()
+        super().setUp()
         
         Agreement.objects.invalidate_cache()
 

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

@@ -4,7 +4,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class UserDataDownloadsApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserDataDownloadsApiTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/data-downloads/' % self.user.pk
 
     def test_get_other_user_exports_anonymous(self):

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

@@ -11,7 +11,7 @@ UserModel = get_user_model()
 
 class UserEditDetailsApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserEditDetailsApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse(
             'misago:api:user-edit-details',

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

@@ -6,7 +6,7 @@ from misago.threads.tests.test_threads_api import ThreadsApiTestCase
 
 class UserThreadsApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(UserThreadsApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse(
             'misago:api:user-threads', kwargs={
@@ -92,7 +92,7 @@ class UserThreadsApiTests(ThreadsApiTestCase):
 
 class UserPostsApiTests(ThreadsApiTestCase):
     def setUp(self):
-        super(UserPostsApiTests, self).setUp()
+        super().setUp()
 
         self.api_link = reverse(
             'misago:api:user-posts', kwargs={

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

@@ -7,7 +7,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class UserMiddlewareTest(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserMiddlewareTest, self).setUp()
+        super().setUp()
 
         self.api_link = reverse('misago:api:auth')
         self.test_link = reverse('misago:index')

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

@@ -1,5 +1,4 @@
-# -*- coding: utf-8 -*-
-from path import Path
+from pathlib import Path
 
 from django.core.exceptions import ValidationError
 from django.test import TestCase
@@ -75,13 +74,13 @@ class UserManagerTests(TestCase):
     def test_getters_unicode_handling(self):
         """get_by_ methods handle unicode"""
         with self.assertRaises(User.DoesNotExist):
-            User.objects.get_by_username(u'łóć')
+            User.objects.get_by_username('łóć')
 
         with self.assertRaises(User.DoesNotExist):
-            User.objects.get_by_email(u'łóć@polskimail.pl')
+            User.objects.get_by_email('łóć@polskimail.pl')
 
         with self.assertRaises(User.DoesNotExist):
-            User.objects.get_by_username_or_email(u'łóć@polskimail.pl')
+            User.objects.get_by_username_or_email('łóć@polskimail.pl')
 
 
 class UserModelTests(TestCase):
@@ -103,7 +102,7 @@ class UserModelTests(TestCase):
         for avatar in user.avatar_set.all():
             avatar_path = Path(avatar.image.path)
             self.assertTrue(avatar_path.exists())
-            self.assertTrue(avatar_path.isfile())
+            self.assertTrue(avatar_path.is_file())
             user_avatars.append(avatar)
         self.assertNotEqual(user_avatars, [])
         
@@ -112,7 +111,7 @@ class UserModelTests(TestCase):
         for removed_avatar in user_avatars:
             avatar_path = Path(removed_avatar.image.path)
             self.assertFalse(avatar_path.exists())
-            self.assertFalse(avatar_path.isfile())
+            self.assertFalse(avatar_path.is_file())
 
             with self.assertRaises(Avatar.DoesNotExist):
                 Avatar.objects.get(pk=removed_avatar.pk)

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

@@ -6,7 +6,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class UserRequestDataDownload(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserRequestDataDownload, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/request-data-download/' % self.user.pk
 
     def test_request_other_user_download_anonymous(self):

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

@@ -6,7 +6,7 @@ class UserSignatureTests(AuthenticatedUserTestCase):
     """tests for user signature RPC (POST to /api/users/1/signature/)"""
 
     def setUp(self):
-        super(UserSignatureTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/signature/' % self.user.pk
 
     def test_signature_no_permission(self):

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

@@ -14,7 +14,7 @@ class UserUsernameTests(AuthenticatedUserTestCase):
     """tests for user change name RPC (POST to /api/users/1/username/)"""
 
     def setUp(self):
-        super(UserUsernameTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/username/' % self.user.pk
 
     def test_get_change_username_options(self):
@@ -109,7 +109,7 @@ class UserUsernameModerationTests(AuthenticatedUserTestCase):
     """tests for moderate username RPC (/api/users/1/moderate-username/)"""
 
     def setUp(self):
-        super(UserUsernameModerationTests, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 

+ 30 - 31
misago/users/tests/test_useradmin_views.py

@@ -1,7 +1,6 @@
 from django.contrib.auth import get_user_model
 from django.core import mail
 from django.urls import reverse
-from django.utils import six
 
 from misago.acl.models import Role
 from misago.admin.testutils import AdminTestCase
@@ -453,8 +452,8 @@ class UserAdminViewsTests(AdminTestCase):
             reverse('misago:admin:users:accounts:new'),
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(default_rank.pk),
-                'roles': six.text_type(authenticated_role.pk),
+                'rank': str(default_rank.pk),
+                'roles': str(authenticated_role.pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'staff_level': '0',
@@ -479,8 +478,8 @@ class UserAdminViewsTests(AdminTestCase):
             reverse('misago:admin:users:accounts:new'),
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(default_rank.pk),
-                'roles': six.text_type(authenticated_role.pk),
+                'rank': str(default_rank.pk),
+                'roles': str(authenticated_role.pk),
                 'email': 'reg@stered.com',
                 'new_password': ' pass123 ',
                 'staff_level': '0',
@@ -509,8 +508,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'newpass123',
                 'staff_level': '0',
@@ -554,8 +553,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bob',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'signature': 'Hello world!',
@@ -591,8 +590,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': ' newpass123 ',
                 'staff_level': '0',
@@ -633,8 +632,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -672,8 +671,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '0',
@@ -718,8 +717,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '0',
@@ -760,8 +759,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -802,8 +801,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '0',
@@ -850,8 +849,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -898,8 +897,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -941,8 +940,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -982,8 +981,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'new_password': 'pass123',
                 'is_staff': '1',
@@ -1022,8 +1021,8 @@ class UserAdminViewsTests(AdminTestCase):
             test_link,
             data={
                 'username': 'Bawww',
-                'rank': six.text_type(test_user.rank_id),
-                'roles': six.text_type(test_user.roles.all()[0].pk),
+                'rank': str(test_user.rank_id),
+                'roles': str(test_user.roles.all()[0].pk),
                 'email': 'reg@stered.com',
                 'is_staff': '1',
                 'is_superuser': '0',

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

@@ -4,7 +4,7 @@ from misago.users.testutils import AuthenticatedUserTestCase
 
 class UsernameChangesApiTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UsernameChangesApiTests, self).setUp()
+        super().setUp()
         self.link = '/api/username-changes/'
 
     def test_user_can_always_see_his_name_changes(self):

+ 11 - 11
misago/users/tests/test_users_api.py

@@ -24,7 +24,7 @@ class ActivePostersListTests(AuthenticatedUserTestCase):
     """tests for active posters list (GET /users/?list=active)"""
 
     def setUp(self):
-        super(ActivePostersListTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/?list=active'
 
         cache.clear()
@@ -71,7 +71,7 @@ class FollowersListTests(AuthenticatedUserTestCase):
     """tests for generic list (GET /users/) filtered by followers"""
 
     def setUp(self):
-        super(FollowersListTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/followers/'
 
     def test_nonexistent_user(self):
@@ -117,7 +117,7 @@ class FollowsListTests(AuthenticatedUserTestCase):
     """tests for generic list (GET /users/) filtered by follows"""
 
     def setUp(self):
-        super(FollowsListTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/follows/'
 
     def test_nonexistent_user(self):
@@ -163,7 +163,7 @@ class RankListTests(AuthenticatedUserTestCase):
     """tests for generic list (GET /users/) filtered by rank"""
 
     def setUp(self):
-        super(RankListTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/?rank=%s'
 
     def test_nonexistent_rank(self):
@@ -237,7 +237,7 @@ class SearchNamesListTests(AuthenticatedUserTestCase):
     """tests for generic list (GET /users/) filtered by username disallowing searches"""
 
     def setUp(self):
-        super(SearchNamesListTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/?&name='
 
     def test_empty_list(self):
@@ -253,7 +253,7 @@ class SearchNamesListTests(AuthenticatedUserTestCase):
 
 class UserRetrieveTests(AuthenticatedUserTestCase):
     def setUp(self):
-        super(UserRetrieveTests, self).setUp()
+        super().setUp()
 
         self.test_user = UserModel.objects.create_user('Tyrael', 't123@test.com', 'pass123')
         self.link = reverse(
@@ -289,7 +289,7 @@ class UserForumOptionsTests(AuthenticatedUserTestCase):
     """tests for user forum options RPC (POST to /api/users/1/forum-options/)"""
 
     def setUp(self):
-        super(UserForumOptionsTests, self).setUp()
+        super().setUp()
         self.link = '/api/users/%s/forum-options/' % self.user.pk
 
     def test_empty_request(self):
@@ -397,7 +397,7 @@ class UserFollowTests(AuthenticatedUserTestCase):
     """tests for user follow RPC (POST to /api/users/1/follow/)"""
 
     def setUp(self):
-        super(UserFollowTests, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 
@@ -461,7 +461,7 @@ class UserBanTests(AuthenticatedUserTestCase):
     """tests for ban endpoint (GET to /api/users/1/ban/)"""
 
     def setUp(self):
-        super(UserBanTests, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 
@@ -503,7 +503,7 @@ class UserBanTests(AuthenticatedUserTestCase):
 class UserDeleteOwnAccountTests(AuthenticatedUserTestCase):
     """tests for user request own account delete RPC (POST to /api/users/1/delete-own-account/)"""
     def setUp(self):
-        super(UserDeleteOwnAccountTests, self).setUp()
+        super().setUp()
         self.api_link = '/api/users/%s/delete-own-account/' % self.user.pk
 
     @override_settings(MISAGO_ENABLE_DELETE_OWN_ACCOUNT=False)
@@ -576,7 +576,7 @@ class UserDeleteTests(AuthenticatedUserTestCase):
     """tests for user delete RPC (POST to /api/users/1/delete/)"""
 
     def setUp(self):
-        super(UserDeleteTests, self).setUp()
+        super().setUp()
 
         self.other_user = UserModel.objects.create_user("OtherUser", "other@user.com", "pass123")
 

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

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.test import TestCase
 
 from misago.users.utils import hash_email
@@ -11,4 +10,4 @@ class HashEmailTests(TestCase):
 
     def test_handles_unicode(self):
         """util works with unicode strings"""
-        self.assertEqual(hash_email(u'łóć@test.com'), hash_email(u'ŁÓĆ@tEst.cOm'))
+        self.assertEqual(hash_email('łóć@test.com'), hash_email('ŁÓĆ@tEst.cOm'))

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

@@ -1,4 +1,3 @@
-#-*- coding: utf-8 -*-
 from django.contrib.auth import get_user_model
 from django.core.exceptions import ValidationError
 from django.test import TestCase
@@ -110,9 +109,9 @@ class ValidateUsernameContentTests(TestCase):
         with self.assertRaises(ValidationError):
             validate_username_content('Bob Boberson')
         with self.assertRaises(ValidationError):
-            validate_username_content(u'Rafał')
+            validate_username_content('Rafał')
         with self.assertRaises(ValidationError):
-            validate_username_content(u'初音 ミク')
+            validate_username_content('初音 ミク')
 
 
 class ValidateUsernameLengthTests(TestCase):

+ 1 - 1
misago/users/testutils.py

@@ -13,7 +13,7 @@ class UserTestCase(MisagoTestCase):
     USER_IP = '127.0.0.1'
 
     def setUp(self):
-        super(UserTestCase, self).setUp()
+        super().setUp()
         self.get_initial_user()
 
     def get_initial_user(self):

+ 1 - 2
misago/users/tokens.py

@@ -12,7 +12,6 @@ from hashlib import sha256
 from time import time
 
 from django.conf import settings
-from django.utils import six
 from django.utils.encoding import force_bytes
 
 
@@ -54,7 +53,7 @@ def _make_hash(user, token_type):
         settings.SECRET_KEY,
     ]
 
-    return sha256(force_bytes('+'.join([six.text_type(s) for s in seeds]))).hexdigest()[:8]
+    return sha256(force_bytes('+'.join([str(s) for s in seeds]))).hexdigest()[:8]
 
 
 def _days_since_epoch():

+ 1 - 1
misago/users/validators.py

@@ -100,7 +100,7 @@ def validate_username(value, exclude=None):
 
 
 # New account validators
-SFS_API_URL = u'http://api.stopforumspam.org/api?email=%(email)s&ip=%(ip)s&f=json&confidence'  # noqa
+SFS_API_URL = 'http://api.stopforumspam.org/api?email=%(email)s&ip=%(ip)s&f=json&confidence'  # noqa
 
 
 def validate_with_sfs(request, cleaned_data, add_error):

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

@@ -14,7 +14,7 @@ class BanAdmin(generic.AdminBaseMixin):
     message_404 = _("Requested ban does not exist.")
 
     def handle_form(self, form, request, target):
-        super(BanAdmin, self).handle_form(form, request, target)
+        super().handle_form(form, request, target)
         Ban.objects.invalidate_cache()
 
 

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

@@ -38,7 +38,7 @@ class DataDownloadsList(DataDownloadAdmin, generic.ListView):
     ]
 
     def get_queryset(self):
-        qs = super(DataDownloadsList, self).get_queryset()
+        qs = super().get_queryset()
         return qs.select_related('user', 'requester')
         
     def get_search_form(self, request):

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

@@ -21,7 +21,7 @@ class RankAdmin(generic.AdminBaseMixin):
             target.roles.add(*roles)
 
     def handle_form(self, form, request, target):
-        super(RankAdmin, self).handle_form(form, request, target)
+        super().handle_form(form, request, target)
         self.update_roles(target, form.cleaned_data['roles'])
 
 

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

@@ -97,7 +97,7 @@ class UsersList(UserAdmin, generic.ListView):
     ]
 
     def get_queryset(self):
-        qs = super(UsersList, self).get_queryset()
+        qs = super().get_queryset()
         return qs.select_related('rank')
 
     def get_search_form(self, request):
@@ -278,7 +278,7 @@ class EditUser(UserAdmin, generic.ModelFormView):
     def real_dispatch(self, request, target):
         target.old_username = target.username
         target.old_is_avatar_locked = target.is_avatar_locked
-        return super(EditUser, self).real_dispatch(request, target)
+        return super().real_dispatch(request, target)
 
     def initialize_form(self, form, request, target):
         if request.method == 'POST':

+ 5 - 4
misago/users/views/auth.py

@@ -1,9 +1,10 @@
+from urllib.parse import urlparse
+
 from django.conf import settings
 from django.contrib import auth
 from django.shortcuts import redirect
 from django.urls import NoReverseMatch
 from django.utils.http import is_safe_url
-from django.utils.six.moves.urllib.parse import urlparse
 from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
 from django.views.decorators.debug import sensitive_post_parameters
@@ -24,10 +25,10 @@ def login(request):
             if is_redirect_safe:
                 redirect_to_path = urlparse(redirect_to).path
                 if '?' not in redirect_to_path:
-                    redirect_to_path = u'{}?'.format(redirect_to_path)
+                    redirect_to_path = '{}?'.format(redirect_to_path)
                 else:
-                    redirect_to_path = u'{}&'.format(redirect_to_path)
-                redirect_to_path = u'{}ref=login'.format(redirect_to_path)
+                    redirect_to_path = '{}&'.format(redirect_to_path)
+                redirect_to_path = '{}ref=login'.format(redirect_to_path)
                 try:
                     return redirect(redirect_to_path)
                 except NoReverseMatch:

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

@@ -1,6 +1,5 @@
 from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
-from django.utils import six
 from django.views import View
 
 from misago.core.utils import format_plaintext_for_html
@@ -24,7 +23,7 @@ class ListView(View):
         for page in sections:
             page['reversed_link'] = reverse(page['link'])
             request.frontend_context['USERS_LISTS'].append({
-                'name': six.text_type(page['name']),
+                'name': str(page['name']),
                 'component': page['component'],
             })
 

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

@@ -1,7 +1,6 @@
 from django.contrib.auth import update_session_auth_hash
 from django.db import IntegrityError
 from django.shortcuts import render
-from django.utils import six
 from django.utils.translation import ugettext as _
 
 from misago.users.credentialchange import read_new_credential
@@ -14,7 +13,7 @@ def index(request, *args, **kwargs):
     user_options = []
     for section in usercp.get_sections(request):
         user_options.append({
-            'name': six.text_type(section['name']),
+            'name': str(section['name']),
             'icon': section['icon'],
             'component': section['component'],
         })

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

@@ -1,7 +1,6 @@
 from django.contrib.auth import get_user_model
 from django.http import Http404
 from django.shortcuts import get_object_or_404, redirect, render
-from django.utils import six
 from django.views import View
 
 from misago.acl import add_acl
@@ -61,7 +60,7 @@ class ProfileView(View):
         request.frontend_context['PROFILE_PAGES'] = []
         for section in sections:
             request.frontend_context['PROFILE_PAGES'].append({
-                'name': six.text_type(section['name']),
+                'name': str(section['name']),
                 'icon': section['icon'],
                 'meta': section.get('metadata'),
                 'component': section['component'],

+ 0 - 1
requirements.in

@@ -10,7 +10,6 @@ Faker<0.9
 html5lib==0.999999999
 markdown<2.7
 misago-social-auth-app-django
-path.py<10.4
 pillow<4.2
 psycopg2-binary<2.8
 pytz

+ 0 - 1
requirements.txt

@@ -22,7 +22,6 @@ markdown==2.6.11
 misago-social-auth-app-django==2.1.0
 oauthlib==2.1.0           # via requests-oauthlib, social-auth-core
 olefile==0.45.1           # via pillow
-path.py==10.3.1
 pillow==4.1.1
 psycopg2-binary==2.7.5
 pyjwt==1.6.4              # via social-auth-core

+ 0 - 3
setup.py

@@ -54,9 +54,6 @@ setup(
         'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Topic :: Internet :: WWW/HTTP',
         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',