Browse Source

Extra cleanups after Django 2.2 (#1229)

* Bump python test version to 3.7

* Fix wrong test

* Fix tests for py3.7

* Remove test request to PlainRequest

* Add migrations removing misago pg partial indexes

* Add django partial indexes

* Remove Misago's PgPartialIndex utility

* Tweak conf middleware test names

* Format code with black
Rafał Pitoń 6 years ago
parent
commit
c980db93b1

+ 3 - 3
.travis.yml

@@ -3,9 +3,9 @@ language: python
 jobs:
 jobs:
   include:
   include:
     - python:
     - python:
-        - 3.6
+        - 3.7
       addons:
       addons:
-        postgresql: 9.4
+        postgresql: 9.6
       install:
       install:
         - pip install -U pip setuptools
         - pip install -U pip setuptools
         - python setup.py install
         - python setup.py install
@@ -17,7 +17,7 @@ jobs:
       after_success:
       after_success:
         - coveralls
         - coveralls
     - name: "lint"
     - name: "lint"
-      python: 3.6
+      python: 3.7
       install:
       install:
         - pip install -U pip setuptools
         - pip install -U pip setuptools
         - pip install black
         - pip install black

+ 1 - 1
Dockerfile

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

+ 26 - 0
misago/categories/migrations/0008_auto_20190518_1659.py

@@ -0,0 +1,26 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_categories", "0007_best_answers_roles")]
+
+    operations = [
+        migrations.AlterField(
+            model_name="category",
+            name="level",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name="category",
+            name="lft",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name="category",
+            name="rght",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+    ]

+ 18 - 22
misago/conf/tests/test_dynamic_settings_middleware.py

@@ -1,4 +1,4 @@
-from unittest.mock import Mock, PropertyMock
+from unittest.mock import Mock
 
 
 import pytest
 import pytest
 from django.utils.functional import SimpleLazyObject
 from django.utils.functional import SimpleLazyObject
@@ -11,49 +11,45 @@ def get_response():
     return Mock()
     return Mock()
 
 
 
 
-@pytest.fixture
-def settings():
-    return PropertyMock()
+class PlainRequest:
+    pass
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def request_mock(settings):
-    request = Mock()
-    type(request).settings = settings
-    return request
+def plain_request():
+    return PlainRequest()
 
 
 
 
-def test_middleware_sets_attr_on_request(db, get_response, request_mock, settings):
+def test_middleware_sets_attr_on_request(db, get_response, plain_request):
     middleware = dynamic_settings_middleware(get_response)
     middleware = dynamic_settings_middleware(get_response)
-    middleware(request_mock)
-    settings.assert_called_once()
+    middleware(plain_request)
+    assert hasattr(plain_request, "settings")
 
 
 
 
 def test_attr_set_by_middleware_on_request_is_lazy_object(
 def test_attr_set_by_middleware_on_request_is_lazy_object(
-    db, get_response, request_mock, settings
+    db, get_response, plain_request
 ):
 ):
     middleware = dynamic_settings_middleware(get_response)
     middleware = dynamic_settings_middleware(get_response)
-    middleware(request_mock)
-    attr_value = settings.call_args[0][0]
-    assert isinstance(attr_value, SimpleLazyObject)
+    middleware(plain_request)
+    assert isinstance(plain_request.settings, SimpleLazyObject)
 
 
 
 
-def test_middleware_calls_get_response(db, get_response, request_mock):
+def test_middleware_calls_get_response(db, get_response, plain_request):
     middleware = dynamic_settings_middleware(get_response)
     middleware = dynamic_settings_middleware(get_response)
-    middleware(request_mock)
+    middleware(plain_request)
     get_response.assert_called_once()
     get_response.assert_called_once()
 
 
 
 
-def test_middleware_is_not_reading_db(
-    db, get_response, request_mock, django_assert_num_queries
+def test_middleware_is_not_reading_from_db(
+    db, get_response, plain_request, django_assert_num_queries
 ):
 ):
     with django_assert_num_queries(0):
     with django_assert_num_queries(0):
         middleware = dynamic_settings_middleware(get_response)
         middleware = dynamic_settings_middleware(get_response)
-        middleware(request_mock)
+        middleware(plain_request)
 
 
 
 
-def test_middleware_is_not_reading_cache(db, mocker, get_response, request_mock):
+def test_middleware_is_not_reading_from_cache(db, mocker, get_response, plain_request):
     cache_get = mocker.patch("django.core.cache.cache.get")
     cache_get = mocker.patch("django.core.cache.cache.get")
     middleware = dynamic_settings_middleware(get_response)
     middleware = dynamic_settings_middleware(get_response)
-    middleware(request_mock)
+    middleware(plain_request)
     cache_get.assert_not_called()
     cache_get.assert_not_called()

+ 0 - 106
misago/core/pgutils.py

@@ -1,109 +1,3 @@
-import hashlib
-
-from django.db.models import Index, Q
-from django.utils.encoding import force_bytes
-
-
-class PgPartialIndex(Index):
-    suffix = "part"
-    max_name_length = 31
-
-    def __init__(self, fields=None, name=None, where=None):
-        if not where:
-            raise ValueError("partial index requires WHERE clause")
-        self.where = where
-
-        if isinstance(where, dict):
-            condition = Q(**where)
-        else:
-            condition = where
-
-        if not name:
-            name = "_".join(where.keys())[:30]
-
-        fields = fields or []
-
-        super().__init__(fields=fields, name=name, condition=condition)
-
-    def set_name_with_model(self, model):
-        table_name = model._meta.db_table
-
-        column_names = sorted(self.where.keys())
-        where_items = []
-        for key in sorted(self.where.keys()):
-            where_items.append("%s:%s" % (key, repr(self.where[key])))
-
-        # The length of the parts of the name is based on the default max
-        # length of 30 characters.
-        hash_data = [table_name] + self.fields + where_items + [self.suffix]
-        self.name = "%s_%s_%s" % (
-            table_name[:11],
-            column_names[0][:7],
-            "%s_%s" % (self._hash_generator(*hash_data), self.suffix),
-        )
-
-        assert len(self.name) <= self.max_name_length, (
-            "Index too long for multiple database support. Is self.suffix "
-            "longer than 3 characters?"
-        )
-        self.check_name()
-
-    @staticmethod
-    def _hash_generator(*args):
-        """
-        Method Index._hash_generator is removed in django 2.2
-        This method is copy from old django 2.1
-        """
-        h = hashlib.md5()
-        for arg in args:
-            h.update(force_bytes(arg))
-
-        return h.hexdigest()[:6]
-
-    def deconstruct(self):
-        path, args, kwargs = super().deconstruct()
-        # TODO: check this patch
-        kwargs["where"] = self.condition
-        del kwargs["condition"]
-        return path, args, kwargs
-
-    def get_sql_create_template_values(self, model, schema_editor, using):
-        parameters = super().get_sql_create_template_values(model, schema_editor, "")
-        parameters["extra"] = self.get_sql_extra(model, schema_editor)
-        return parameters
-
-    def get_sql_extra(self, model, schema_editor):
-        quote_name = schema_editor.quote_name
-        quote_value = schema_editor.quote_value
-
-        clauses = []
-        for field, condition in self.where.items():
-            field_name = None
-            compr = None
-            if field.endswith("__lt"):
-                field_name = field[:-4]
-                compr = "<"
-            elif field.endswith("__gt"):
-                field_name = field[:-4]
-                compr = ">"
-            elif field.endswith("__lte"):
-                field_name = field[:-5]
-                compr = "<="
-            elif field.endswith("__gte"):
-                field_name = field[:-5]
-                compr = ">="
-            else:
-                field_name = field
-                compr = "="
-
-            column = model._meta.get_field(field_name).column
-            clauses.append(
-                "%s %s %s" % (quote_name(column), compr, quote_value(condition))
-            )
-        # sort clauses for their order to be determined and testable
-        return " WHERE %s" % (" AND ".join(sorted(clauses)))
-
-
 def chunk_queryset(queryset, chunk_size=20):
 def chunk_queryset(queryset, chunk_size=20):
     ordered_queryset = queryset.order_by("-pk")  # bias to newest items first
     ordered_queryset = queryset.order_by("-pk")  # bias to newest items first
     chunk = ordered_queryset[:chunk_size]
     chunk = ordered_queryset[:chunk_size]

+ 0 - 101
misago/core/tests/test_pgpartialindex.py

@@ -1,101 +0,0 @@
-from django.db import connection
-from django.test import TestCase
-
-from ...threads.models import Thread
-from ..pgutils import PgPartialIndex
-
-
-class PgPartialIndexTests(TestCase):
-    def test_multiple_fields(self):
-        """multiple fields are supported"""
-        with connection.schema_editor() as editor:
-            sql = PgPartialIndex(
-                fields=["has_events", "is_hidden"],
-                name="test_partial",
-                where={"has_events": True},
-            ).create_sql(Thread, editor)
-
-            self.assertIn(
-                'CREATE INDEX "test_partial" ON "misago_threads_thread"', repr(sql)
-            )
-            self.assertIn(
-                'ON "misago_threads_thread" ("has_events", "is_hidden")', repr(sql)
-            )
-
-    def test_where_clauses(self):
-        """where clauses generate correctly"""
-        with connection.schema_editor() as editor:
-            sql = PgPartialIndex(
-                fields=["has_events"], name="test_partial", where={"has_events": True}
-            ).create_sql(Thread, editor)
-
-            self.assertTrue(
-                str(sql).endswith('WHERE "misago_threads_thread"."has_events" = true')
-            )
-
-            sql = PgPartialIndex(
-                fields=["has_events"], name="test_partial", where={"has_events": False}
-            ).create_sql(Thread, editor)
-            self.assertTrue(
-                str(sql).endswith('WHERE "misago_threads_thread"."has_events" = false')
-            )
-
-    def test_multiple_where_clauses(self):
-        """where clause with multiple conditions generates correctly"""
-        with connection.schema_editor() as editor:
-            sql = PgPartialIndex(
-                fields=["has_events"],
-                name="test_partial",
-                where={"has_events": True, "is_hidden": True},
-            ).create_sql(Thread, editor)
-            self.assertEqual(
-                'CREATE INDEX "test_partial" ON "misago_threads_thread" ("has_events") WHERE '
-                '("misago_threads_thread"."has_events" = true AND '
-                '"misago_threads_thread"."is_hidden" = true)',
-                str(sql),
-            )
-
-    def test_set_name_with_model(self):
-        """valid index name is autogenerated"""
-        index = PgPartialIndex(
-            fields=["has_events", "is_hidden"], where={"has_events": True}
-        )
-        index.set_name_with_model(Thread)
-        self.assertEqual(index.name, "misago_thre_has_eve_1b05b8_part")
-
-        index = PgPartialIndex(
-            fields=["has_events", "is_hidden", "is_closed"], where={"has_events": True}
-        )
-        index.set_name_with_model(Thread)
-        self.assertEqual(index.name, "misago_thre_has_eve_eaab5e_part")
-
-        index = PgPartialIndex(
-            fields=["has_events", "is_hidden", "is_closed"],
-            where={"has_events": True, "is_closed": False},
-        )
-        index.set_name_with_model(Thread)
-        self.assertEqual(index.name, "misago_thre_has_eve_e738fe_part")
-
-    def test_index_repr(self):
-        """index creates descriptive representation string"""
-        index = PgPartialIndex(fields=["has_events"], where={"has_events": True})
-        self.assertEqual(
-            repr(index),
-            "<PgPartialIndex: fields='has_events', condition=(AND: ('has_events', True))>",
-        )
-
-        index = PgPartialIndex(
-            fields=["has_events", "is_hidden"], where={"has_events": True}
-        )
-        self.assertIn("fields='has_events, is_hidden',", repr(index))
-        self.assertIn(", condition=(AND: ('has_events', True))>", repr(index))
-
-        index = PgPartialIndex(
-            fields=["has_events", "is_hidden", "is_closed"],
-            where={"has_events": True, "is_closed": False, "replies__gte": 5},
-        )
-        self.assertIn("fields='has_events, is_hidden, is_closed',", repr(index))
-        self.assertIn(
-            ", condition=(AND: ('has_events', True), ('is_closed', False), ('replies__gte', 5))>",
-            repr(index),
-        )

+ 26 - 0
misago/themes/migrations/0003_auto_20190518_1659.py

@@ -0,0 +1,26 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_themes", "0002_create_default_theme_and_cache_version")]
+
+    operations = [
+        migrations.AlterField(
+            model_name="theme",
+            name="level",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name="theme",
+            name="lft",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+        migrations.AlterField(
+            model_name="theme",
+            name="rght",
+            field=models.PositiveIntegerField(editable=False),
+        ),
+    ]

+ 2 - 2
misago/threads/api/threadpoll.py

@@ -3,7 +3,7 @@ from django.db import transaction
 from django.http import Http404
 from django.http import Http404
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 from rest_framework import viewsets
 from rest_framework import viewsets
-from rest_framework.decorators import detail_route
+from rest_framework.decorators import action
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from ...acl.objectacl import add_acl_to_obj
 from ...acl.objectacl import add_acl_to_obj
@@ -118,7 +118,7 @@ class ViewSet(viewsets.ViewSet):
 
 
         return Response({"can_start_poll": can_start_poll(request.user_acl, thread)})
         return Response({"can_start_poll": can_start_poll(request.user_acl, thread)})
 
 
-    @detail_route(methods=["get", "post"])
+    @action(detail=True, methods=["get", "post"])
     def votes(self, request, thread_pk, pk=None):
     def votes(self, request, thread_pk, pk=None):
         if request.method == "POST":
         if request.method == "POST":
             return self.post_votes(request, thread_pk, pk)
             return self.post_votes(request, thread_pk, pk)

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

@@ -2,7 +2,7 @@ from django.core.exceptions import PermissionDenied
 from django.db import transaction
 from django.db import transaction
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 from rest_framework import viewsets
 from rest_framework import viewsets
-from rest_framework.decorators import detail_route, list_route
+from rest_framework.decorators import action
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from ...acl.objectacl import add_acl_to_obj
 from ...acl.objectacl import add_acl_to_obj
@@ -65,19 +65,19 @@ class ViewSet(viewsets.ViewSet):
 
 
         return Response(data)
         return Response(data)
 
 
-    @list_route(methods=["post"])
+    @action(detail=False, methods=["post"])
     @transaction.atomic
     @transaction.atomic
     def merge(self, request, thread_pk):
     def merge(self, request, thread_pk):
         thread = self.get_thread(request, thread_pk).unwrap()
         thread = self.get_thread(request, thread_pk).unwrap()
         return posts_merge_endpoint(request, thread)
         return posts_merge_endpoint(request, thread)
 
 
-    @list_route(methods=["post"])
+    @action(detail=False, methods=["post"])
     @transaction.atomic
     @transaction.atomic
     def move(self, request, thread_pk):
     def move(self, request, thread_pk):
         thread = self.get_thread(request, thread_pk).unwrap()
         thread = self.get_thread(request, thread_pk).unwrap()
         return posts_move_endpoint(request, thread, self.thread)
         return posts_move_endpoint(request, thread, self.thread)
 
 
-    @list_route(methods=["post"])
+    @action(detail=False, methods=["post"])
     @transaction.atomic
     @transaction.atomic
     def split(self, request, thread_pk):
     def split(self, request, thread_pk):
         thread = self.get_thread(request, thread_pk).unwrap()
         thread = self.get_thread(request, thread_pk).unwrap()
@@ -161,13 +161,13 @@ class ViewSet(viewsets.ViewSet):
 
 
         return delete_bulk(request, thread.unwrap())
         return delete_bulk(request, thread.unwrap())
 
 
-    @detail_route(methods=["post"])
+    @action(detail=True, methods=["post"])
     def read(self, request, thread_pk, pk=None):
     def read(self, request, thread_pk, pk=None):
         thread = self.get_thread(request, thread_pk, subscription_aware=True).unwrap()
         thread = self.get_thread(request, thread_pk, subscription_aware=True).unwrap()
         post = self.get_post(request, thread, pk).unwrap()
         post = self.get_post(request, thread, pk).unwrap()
         return post_read_endpoint(request, thread, post)
         return post_read_endpoint(request, thread, post)
 
 
-    @detail_route(methods=["get"], url_path="editor")
+    @action(detail=True, methods=["get"], url_name="editor")
     def post_editor(self, request, thread_pk, pk=None):
     def post_editor(self, request, thread_pk, pk=None):
         thread = self.get_thread(request, thread_pk)
         thread = self.get_thread(request, thread_pk)
         post = self.get_post(request, thread, pk).unwrap()
         post = self.get_post(request, thread, pk).unwrap()
@@ -194,7 +194,7 @@ class ViewSet(viewsets.ViewSet):
             }
             }
         )
         )
 
 
-    @list_route(methods=["get"], url_path="editor")
+    @action(detail=False, methods=["get"], url_name="editor")
     def reply_editor(self, request, thread_pk):
     def reply_editor(self, request, thread_pk):
         thread = self.get_thread(request, thread_pk).unwrap()
         thread = self.get_thread(request, thread_pk).unwrap()
         allow_reply_thread(request.user_acl, thread)
         allow_reply_thread(request.user_acl, thread)
@@ -219,7 +219,7 @@ class ViewSet(viewsets.ViewSet):
             }
             }
         )
         )
 
 
-    @detail_route(methods=["get", "post"])
+    @action(detail=True, methods=["get", "post"])
     def edits(self, request, thread_pk, pk=None):
     def edits(self, request, thread_pk, pk=None):
         if request.method == "GET":
         if request.method == "GET":
             thread = self.get_thread(request, thread_pk)
             thread = self.get_thread(request, thread_pk)
@@ -236,7 +236,7 @@ class ViewSet(viewsets.ViewSet):
 
 
                 return revert_post_endpoint(request, post)
                 return revert_post_endpoint(request, post)
 
 
-    @detail_route(methods=["get"])
+    @action(detail=True, methods=["get"])
     def likes(self, request, thread_pk, pk=None):
     def likes(self, request, thread_pk, pk=None):
         thread = self.get_thread(request, thread_pk)
         thread = self.get_thread(request, thread_pk)
         post = self.get_post(request, thread, pk).unwrap()
         post = self.get_post(request, thread, pk).unwrap()

+ 1 - 78
misago/threads/migrations/0006_redo_partial_indexes.py

@@ -1,86 +1,9 @@
 # Generated by Django 1.11.1 on 2017-05-21 17:52
 # Generated by Django 1.11.1 on 2017-05-21 17:52
-import django.contrib.postgres.indexes
-from django.contrib.postgres.operations import BtreeGinExtension
 from django.db import migrations
 from django.db import migrations
 
 
-import misago.core.pgutils
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_threads", "0005_index_search_document")]
     dependencies = [("misago_threads", "0005_index_search_document")]
 
 
-    operations = [
-        migrations.AddIndex(
-            model_name="post",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["has_open_reports"],
-                name="misago_thre_has_ope_479906_part",
-                where={"has_open_reports": True},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="post",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["is_hidden"],
-                name="misago_thre_is_hidd_85db69_part",
-                where={"is_hidden": False},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["weight"],
-                name="misago_thre_weight_955884_part",
-                where={"weight": 2},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["weight"],
-                name="misago_thre_weight_9e8f9c_part",
-                where={"weight": 1},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["weight"],
-                name="misago_thre_weight_c7ef29_part",
-                where={"weight": 0},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["weight"],
-                name="misago_thre_weight__4af9ee_part",
-                where={"weight__lt": 2},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["has_reported_posts"],
-                name="misago_thre_has_rep_84acfa_part",
-                where={"has_reported_posts": True},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["has_unapproved_posts"],
-                name="misago_thre_has_una_b0dbf5_part",
-                where={"has_unapproved_posts": True},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="thread",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["is_hidden"],
-                name="misago_thre_is_hidd_d2b96c_part",
-                where={"is_hidden": False},
-            ),
-        ),
-    ]
+    operations = []

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

@@ -1,20 +1,9 @@
 # Generated by Django 1.11.9 on 2018-03-26 00:10
 # Generated by Django 1.11.9 on 2018-03-26 00:10
 from django.db import migrations
 from django.db import migrations
 
 
-import misago.core.pgutils
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_threads", "0008_auto_20180310_2234")]
     dependencies = [("misago_threads", "0008_auto_20180310_2234")]
 
 
-    operations = [
-        migrations.AddIndex(
-            model_name="post",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["is_event", "event_type"],
-                name="misago_thre_is_even_42bda7_part",
-                where={"is_event": True},
-            ),
-        )
-    ]
+    operations = []

+ 21 - 0
misago/threads/migrations/0011_remove_custom_partial_indexes.py

@@ -0,0 +1,21 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:31
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_threads", "0010_auto_20180609_1523")]
+
+    operations = [
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_has_ope_479906_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_is_hidd_85db69_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_is_even_42bda7_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_weight_955884_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_weight_9e8f9c_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_weight_c7ef29_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_weight__4af9ee_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_has_rep_84acfa_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_has_una_b0dbf5_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_thre_is_hidd_d2b96c_part"),
+    ]

+ 91 - 0
misago/threads/migrations/0012_set_dj_partial_indexes.py

@@ -0,0 +1,91 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_threads", "0011_remove_custom_partial_indexes")]
+
+    operations = [
+        migrations.AddIndex(
+            model_name="post",
+            index=models.Index(
+                condition=models.Q(has_open_reports=True),
+                fields=["has_open_reports"],
+                name="misago_post_has_open_repo_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="post",
+            index=models.Index(
+                condition=models.Q(is_hidden=False),
+                fields=["is_hidden"],
+                name="misago_post_is_hidden_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="post",
+            index=models.Index(
+                condition=models.Q(is_event=True),
+                fields=["is_event", "event_type"],
+                name="misago_post_is_event_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(weight=2),
+                fields=["weight"],
+                name="misago_thread_pinned_glob_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(weight=1),
+                fields=["weight"],
+                name="misago_thread_pinned_loca_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(weight=0),
+                fields=["weight"],
+                name="misago_thread_not_pinned_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(weight__lt=2),
+                fields=["weight"],
+                name="misago_thread_not_global_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(has_reported_posts=True),
+                fields=["has_reported_posts"],
+                name="misago_thread_has_reporte_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(has_unapproved_posts=True),
+                fields=["has_unapproved_posts"],
+                name="misago_thread_has_unappro_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="thread",
+            index=models.Index(
+                condition=models.Q(is_hidden=False),
+                fields=["is_hidden"],
+                name="misago_thread_is_visible_part",
+            ),
+        ),
+    ]

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

@@ -4,10 +4,10 @@ from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.indexes import GinIndex
 from django.contrib.postgres.indexes import GinIndex
 from django.contrib.postgres.search import SearchVector, SearchVectorField
 from django.contrib.postgres.search import SearchVector, SearchVectorField
 from django.db import models
 from django.db import models
+from django.db.models import Q
 from django.utils import timezone
 from django.utils import timezone
 
 
 from ...conf import settings
 from ...conf import settings
-from ...core.pgutils import PgPartialIndex
 from ...core.utils import parse_iso8601_string
 from ...core.utils import parse_iso8601_string
 from ...markup import finalise_markup
 from ...markup import finalise_markup
 from ..checksums import is_post_valid, update_post_checksum
 from ..checksums import is_post_valid, update_post_checksum
@@ -79,11 +79,21 @@ class Post(models.Model):
 
 
     class Meta:
     class Meta:
         indexes = [
         indexes = [
-            PgPartialIndex(
-                fields=["has_open_reports"], where={"has_open_reports": True}
+            models.Index(
+                name="misago_post_has_open_repo_part",
+                fields=["has_open_reports"],
+                condition=Q(has_open_reports=True),
+            ),
+            models.Index(
+                name="misago_post_is_hidden_part",
+                fields=["is_hidden"],
+                condition=Q(is_hidden=False),
+            ),
+            models.Index(
+                name="misago_post_is_event_part",
+                fields=["is_event", "event_type"],
+                condition=Q(is_event=True),
             ),
             ),
-            PgPartialIndex(fields=["is_hidden"], where={"is_hidden": False}),
-            PgPartialIndex(fields=["is_event", "event_type"], where={"is_event": True}),
             GinIndex(fields=["search_vector"]),
             GinIndex(fields=["search_vector"]),
         ]
         ]
 
 

+ 34 - 10
misago/threads/models/thread.py

@@ -1,10 +1,10 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from django.db import models
 from django.db import models
+from django.db.models import Q
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
 from ...conf import settings
 from ...conf import settings
-from ...core.pgutils import PgPartialIndex
 from ...core.utils import slugify
 from ...core.utils import slugify
 
 
 
 
@@ -99,17 +99,41 @@ class Thread(models.Model):
 
 
     class Meta:
     class Meta:
         indexes = [
         indexes = [
-            PgPartialIndex(fields=["weight"], where={"weight": 2}),
-            PgPartialIndex(fields=["weight"], where={"weight": 1}),
-            PgPartialIndex(fields=["weight"], where={"weight": 0}),
-            PgPartialIndex(fields=["weight"], where={"weight__lt": 2}),
-            PgPartialIndex(
-                fields=["has_reported_posts"], where={"has_reported_posts": True}
+            models.Index(
+                name="misago_thread_pinned_glob_part",
+                fields=["weight"],
+                condition=Q(weight=2),
             ),
             ),
-            PgPartialIndex(
-                fields=["has_unapproved_posts"], where={"has_unapproved_posts": True}
+            models.Index(
+                name="misago_thread_pinned_loca_part",
+                fields=["weight"],
+                condition=Q(weight=1),
+            ),
+            models.Index(
+                name="misago_thread_not_pinned_part",
+                fields=["weight"],
+                condition=Q(weight=0),
+            ),
+            models.Index(
+                name="misago_thread_not_global_part",
+                fields=["weight"],
+                condition=Q(weight__lt=2),
+            ),
+            models.Index(
+                name="misago_thread_has_reporte_part",
+                fields=["has_reported_posts"],
+                condition=Q(has_reported_posts=True),
+            ),
+            models.Index(
+                name="misago_thread_has_unappro_part",
+                fields=["has_unapproved_posts"],
+                condition=Q(has_unapproved_posts=True),
+            ),
+            models.Index(
+                name="misago_thread_is_visible_part",
+                fields=["is_hidden"],
+                condition=Q(is_hidden=False),
             ),
             ),
-            PgPartialIndex(fields=["is_hidden"], where={"is_hidden": False}),
         ]
         ]
 
 
         index_together = [
         index_together = [

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

@@ -24,8 +24,12 @@ def create_endpoint(request):
     if request.settings.account_activation == "closed":
     if request.settings.account_activation == "closed":
         raise PermissionDenied(_("New users registrations are currently closed."))
         raise PermissionDenied(_("New users registrations are currently closed."))
 
 
+    request_data = request.data
+    if not isinstance(request_data, dict):
+        request_data = {}
+
     form = RegisterForm(
     form = RegisterForm(
-        request.data, request=request, agreements=Agreement.objects.get_agreements()
+        request_data, request=request, agreements=Agreement.objects.get_agreements()
     )
     )
 
 
     try:
     try:

+ 1 - 20
misago/users/migrations/0009_redo_partial_indexes.py

@@ -1,28 +1,9 @@
 # Generated by Django 1.11.1 on 2017-05-26 21:56
 # Generated by Django 1.11.1 on 2017-05-26 21:56
 from django.db import migrations
 from django.db import migrations
 
 
-import misago.core.pgutils
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_users", "0008_ban_registration_only")]
     dependencies = [("misago_users", "0008_ban_registration_only")]
 
 
-    operations = [
-        migrations.AddIndex(
-            model_name="user",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["is_staff"],
-                name="misago_user_is_staf_bf68aa_part",
-                where={"is_staff": True},
-            ),
-        ),
-        migrations.AddIndex(
-            model_name="user",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["requires_activation"],
-                name="misago_user_require_05204a_part",
-                where={"requires_activation__gt": 0},
-            ),
-        ),
-    ]
+    operations = []

+ 1 - 11
misago/users/migrations/0011_auto_20180331_2208.py

@@ -1,8 +1,6 @@
 # Generated by Django 1.11.11 on 2018-03-31 22:08
 # Generated by Django 1.11.11 on 2018-03-31 22:08
 from django.db import migrations, models
 from django.db import migrations, models
 
 
-import misago.core.pgutils
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
@@ -13,13 +11,5 @@ class Migration(migrations.Migration):
             model_name="user",
             model_name="user",
             name="is_deleting_account",
             name="is_deleting_account",
             field=models.BooleanField(default=False),
             field=models.BooleanField(default=False),
-        ),
-        migrations.AddIndex(
-            model_name="user",
-            index=misago.core.pgutils.PgPartialIndex(
-                fields=["is_deleting_account"],
-                name="misago_user_is_dele_2798b0_part",
-                where={"is_deleting_account": True},
-            ),
-        ),
+        )
     ]
     ]

+ 14 - 0
misago/users/migrations/0019_remove_custom_partial_indexes.py

@@ -0,0 +1,14 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:31
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_users", "0018_auto_20190511_2051")]
+
+    operations = [
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_user_is_staf_bf68aa_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_user_require_05204a_part"),
+        migrations.RunSQL("DROP INDEX IF EXISTS misago_user_is_dele_2798b0_part"),
+    ]

+ 35 - 0
misago/users/migrations/0020_set_dj_partial_indexes.py

@@ -0,0 +1,35 @@
+# Generated by Django 2.2.1 on 2019-05-18 16:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_users", "0019_remove_custom_partial_indexes")]
+
+    operations = [
+        migrations.AddIndex(
+            model_name="user",
+            index=models.Index(
+                condition=models.Q(is_staff=True),
+                fields=["is_staff"],
+                name="misago_user_is_staff_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="user",
+            index=models.Index(
+                condition=models.Q(requires_activation__gt=0),
+                fields=["requires_activation"],
+                name="misago_user_requires_acti_part",
+            ),
+        ),
+        migrations.AddIndex(
+            model_name="user",
+            index=models.Index(
+                condition=models.Q(is_deleting_account=True),
+                fields=["is_deleting_account"],
+                name="misago_user_is_deleting_a_part",
+            ),
+        ),
+    ]

+ 14 - 6
misago/users/models/user.py

@@ -7,6 +7,7 @@ from django.contrib.auth.models import UserManager as BaseUserManager
 from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField
 from django.contrib.postgres.fields import ArrayField, HStoreField, JSONField
 from django.core.mail import send_mail
 from django.core.mail import send_mail
 from django.db import models
 from django.db import models
+from django.db.models import Q
 from django.urls import reverse
 from django.urls import reverse
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
@@ -14,7 +15,6 @@ from django.utils.translation import gettext_lazy as _
 from .. import avatars
 from .. import avatars
 from ...acl.models import Role
 from ...acl.models import Role
 from ...conf import settings
 from ...conf import settings
-from ...core.pgutils import PgPartialIndex
 from ...core.utils import slugify
 from ...core.utils import slugify
 from ..signatures import is_user_signature_valid
 from ..signatures import is_user_signature_valid
 from ..utils import hash_email
 from ..utils import hash_email
@@ -223,12 +223,20 @@ class User(AbstractBaseUser, PermissionsMixin):
 
 
     class Meta:
     class Meta:
         indexes = [
         indexes = [
-            PgPartialIndex(fields=["is_staff"], where={"is_staff": True}),
-            PgPartialIndex(
-                fields=["requires_activation"], where={"requires_activation__gt": 0}
+            models.Index(
+                name="misago_user_is_staff_part",
+                fields=["is_staff"],
+                condition=Q(is_staff=True),
             ),
             ),
-            PgPartialIndex(
-                fields=["is_deleting_account"], where={"is_deleting_account": True}
+            models.Index(
+                name="misago_user_requires_acti_part",
+                fields=["requires_activation"],
+                condition=Q(requires_activation__gt=0),
+            ),
+            models.Index(
+                name="misago_user_is_deleting_a_part",
+                fields=["is_deleting_account"],
+                condition=Q(is_deleting_account=True),
             ),
             ),
         ]
         ]
 
 

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

@@ -38,7 +38,9 @@ class UserCreateTests(UserTestCase):
 
 
     def test_invalid_data(self):
     def test_invalid_data(self):
         """invalid request data errors with code 400"""
         """invalid request data errors with code 400"""
-        response = self.client.post(self.api_link, {}, content_type="application/json")
+        response = self.client.post(
+            self.api_link, "false", content_type="application/json"
+        )
         self.assertEqual(response.status_code, 400)
         self.assertEqual(response.status_code, 400)
         self.assertEqual(
         self.assertEqual(
             response.json(),
             response.json(),