Browse Source

Remove settings groups and other metadata (#1231)

* Remove settings models, WIP settings reset migration

* Remove default value

* Simplify conf structure, move tests to pytest

* Format code with black

* Remove unused field_extra
Rafał Pitoń 6 years ago
parent
commit
7ebbac3048

+ 8 - 8
misago/conf/hydrators.py

@@ -1,12 +1,10 @@
-# fixme: rename this moduleto serialize
-
-
+# fixme: rename this module to serialize
 def hydrate_string(dry_value):
 def hydrate_string(dry_value):
     return str(dry_value) if dry_value else ""
     return str(dry_value) if dry_value else ""
 
 
 
 
 def dehydrate_string(wet_value):
 def dehydrate_string(wet_value):
-    return wet_value
+    return str(wet_value)
 
 
 
 
 def hydrate_bool(dry_value):
 def hydrate_bool(dry_value):
@@ -18,19 +16,21 @@ def dehydrate_bool(wet_value):
 
 
 
 
 def hydrate_int(dry_value):
 def hydrate_int(dry_value):
-    return int(dry_value)
+    return int(dry_value or 0)
 
 
 
 
 def dehydrate_int(wet_value):
 def dehydrate_int(wet_value):
-    return str(wet_value)
+    return str(wet_value or 0)
 
 
 
 
 def hydrate_list(dry_value):
 def hydrate_list(dry_value):
-    return [x for x in dry_value.split(",") if x]
+    if dry_value:
+        return [x for x in dry_value.split(",") if x]
+    return []
 
 
 
 
 def dehydrate_list(wet_value):
 def dehydrate_list(wet_value):
-    return ",".join(wet_value)
+    return ",".join(wet_value) if wet_value else ""
 
 
 
 
 VALUE_HYDRATORS = {
 VALUE_HYDRATORS = {

+ 29 - 0
misago/conf/migrations/0003_simplify_models.py

@@ -0,0 +1,29 @@
+# Generated by Django 2.2.1 on 2019-05-19 00:08
+
+from django.db import migrations
+from django.db.models import F
+
+
+def set_default_dry_value(apps, _):
+    Setting = apps.get_model("misago_conf", "Setting")
+    Setting.objects.filter(dry_value__isnull=True, default_value__isnull=False).update(
+        dry_value=F("default_value")
+    )
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [("misago_conf", "0002_cache_version")]
+
+    operations = [
+        migrations.RemoveField(model_name="setting", name="description"),
+        migrations.RemoveField(model_name="setting", name="field_extra"),
+        migrations.RemoveField(model_name="setting", name="form_field"),
+        migrations.RemoveField(model_name="setting", name="group"),
+        migrations.RemoveField(model_name="setting", name="legend"),
+        migrations.RemoveField(model_name="setting", name="name"),
+        migrations.RemoveField(model_name="setting", name="order"),
+        migrations.RunPython(set_default_dry_value),
+        migrations.RemoveField(model_name="setting", name="default_value"),
+        migrations.DeleteModel(name="SettingsGroup"),
+    ]

+ 100 - 0
misago/conf/migrations/0004_create_settings.py

@@ -0,0 +1,100 @@
+# Generated by Django 2.2.1 on 2019-05-19 00:16
+
+from django.db import migrations
+
+from ..hydrators import dehydrate_value
+
+
+default_settings = [
+    {"setting": "account_activation", "dry_value": "none", "is_public": True},
+    {"setting": "allow_custom_avatars", "python_type": "bool", "dry_value": True},
+    {
+        "setting": "avatar_upload_limit",
+        "python_type": "int",
+        "dry_value": 1536,
+        "is_public": True,
+    },
+    {"setting": "captcha_type", "dry_value": "no", "is_public": True},
+    {"setting": "default_avatar", "dry_value": "gravatar"},
+    {"setting": "default_gravatar_fallback", "dry_value": "dynamic"},
+    {"setting": "email_footer"},
+    {
+        "setting": "forum_branding_display",
+        "dry_value": True,
+        "python_type": "bool",
+        "is_public": True,
+    },
+    {"setting": "forum_branding_text", "dry_value": "Misago", "is_public": True},
+    {"setting": "forum_footnote", "is_public": True},
+    {"setting": "forum_index_meta_description"},
+    {"setting": "forum_index_title", "is_public": True},
+    {"setting": "forum_name", "dry_value": "Misago", "is_public": True},
+    {
+        "setting": "post_length_max",
+        "python_type": "int",
+        "dry_value": 60000,
+        "is_public": True,
+    },
+    {
+        "setting": "post_length_min",
+        "python_type": "int",
+        "dry_value": 5,
+        "is_public": True,
+    },
+    {"setting": "qa_answers"},
+    {"setting": "qa_help_text"},
+    {"setting": "qa_question"},
+    {"setting": "recaptcha_secret_key"},
+    {"setting": "recaptcha_site_key", "is_public": True},
+    {
+        "setting": "signature_length_max",
+        "python_type": "int",
+        "dry_value": 256,
+        "is_public": True,
+    },
+    {"setting": "subscribe_reply", "dry_value": "watch_email"},
+    {"setting": "subscribe_start", "dry_value": "watch_email"},
+    {
+        "setting": "thread_title_length_max",
+        "python_type": "int",
+        "dry_value": 90,
+        "is_public": True,
+    },
+    {
+        "setting": "thread_title_length_min",
+        "python_type": "int",
+        "dry_value": 5,
+        "is_public": True,
+    },
+    {"setting": "username_length_min", "python_type": "int", "dry_value": 3},
+    {"setting": "username_length_max", "python_type": "int", "dry_value": 14},
+]
+
+
+def create_settings(apps, _):
+    # This migration builds list of existing settings, and then
+    # creates settings not already in the database
+    Setting = apps.get_model("misago_conf", "Setting")
+    existing_settings = list(Setting.objects.values_list("setting", flat=True))
+
+    for setting in default_settings:
+        if setting["setting"] in existing_settings:
+            continue  # skip already existing setting (migration on existing forum)
+
+        data = setting.copy()
+        if "python_type" in data and "dry_value" in data:
+            data["dry_value"] = dehydrate_value(data["python_type"], data["dry_value"])
+
+        Setting.objects.create(**setting)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("misago_conf", "0003_simplify_models"),
+        ("misago_core", "0003_delete_cacheversion"),
+        ("misago_threads", "0012_set_dj_partial_indexes"),
+        ("misago_users", "0020_set_dj_partial_indexes"),
+    ]
+
+    operations = [migrations.RunPython(create_settings)]

+ 0 - 92
misago/conf/migrationutils.py

@@ -1,92 +0,0 @@
-from .hydrators import dehydrate_value
-from .utils import get_setting_value, has_custom_value
-
-
-def migrate_settings_group(apps, group_fixture, old_group_key=None):
-    SettingsGroup = apps.get_model("misago_conf", "SettingsGroup")
-    Setting = apps.get_model("misago_conf", "Setting")
-    group_key = group_fixture["key"]
-
-    # Fetch settings group
-
-    if old_group_key:
-        group = get_group(SettingsGroup, old_group_key)
-        custom_settings_values = get_custom_settings_values(group)
-    else:
-        group = get_group(SettingsGroup, group_key)
-        if group.pk:
-            custom_settings_values = get_custom_settings_values(group)
-        else:
-            custom_settings_values = {}
-
-    # Update group's attributes
-
-    group.key = group_fixture["key"]
-    group.name = group_fixture["name"]
-    if group_fixture.get("description"):
-        group.description = group_fixture.get("description")
-    group.save()
-
-    # Delete groups settings and make new ones
-    # Its easier to create news ones and then assign them old values
-
-    group.setting_set.all().delete()
-
-    for order, setting_fixture in enumerate(group_fixture["settings"]):
-        old_value = custom_settings_values.pop(setting_fixture["setting"], None)
-        migrate_setting(Setting, group, setting_fixture, order, old_value)
-
-
-def get_group(SettingsGroup, group_key):
-    try:
-        return SettingsGroup.objects.get(key=group_key)
-    except SettingsGroup.DoesNotExist:
-        return SettingsGroup()
-
-
-def get_custom_settings_values(group):
-    custom_settings_values = {}
-
-    for setting in group.setting_set.iterator():
-        if has_custom_value(setting):
-            custom_settings_values[setting.setting] = get_setting_value(setting)
-
-    return custom_settings_values
-
-
-def migrate_setting(Setting, group, setting_fixture, order, old_value):
-    setting_fixture["group"] = group
-    setting_fixture["order"] = order
-
-    setting_fixture["name"] = setting_fixture["name"]
-    if setting_fixture.get("description"):
-        setting_fixture["description"] = setting_fixture.get("description")
-
-    if setting_fixture.get("field_extra") and setting_fixture.get("field_extra").get(
-        "choices"
-    ):
-        untranslated_choices = setting_fixture["field_extra"]["choices"]
-        translated_choices = []
-        for val, name in untranslated_choices:
-            translated_choices.append((val, name))
-        setting_fixture["field_extra"]["choices"] = tuple(translated_choices)
-
-    if old_value is None:
-        value = setting_fixture.pop("value", None)
-    else:
-        value = old_value
-    setting_fixture.pop("value", None)
-
-    field_extra = setting_fixture.pop("field_extra", None)
-
-    setting = Setting(**setting_fixture)
-    setting.dry_value = dehydrate_value(setting.python_type, value)
-
-    if setting_fixture.get("default_value"):
-        setting.default_value = dehydrate_value(
-            setting.python_type, setting_fixture.get("default_value")
-        )
-
-    setting.field_extra = field_extra or {}
-
-    setting.save()

+ 7 - 45
misago/conf/models.py

@@ -1,30 +1,7 @@
 from django.contrib.postgres.fields import JSONField
 from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.db import models
 
 
-from . import utils
-
-
-class SettingsGroupsManager(models.Manager):
-    def ordered_alphabetically(self):
-        from django.utils.translation import gettext as _
-
-        groups_dict = {}
-
-        for group in self.all():
-            groups_dict[_(group.name)] = group
-
-        ordered_groups = []
-        for key in groups_dict:
-            ordered_groups.append(groups_dict[key])
-        return ordered_groups
-
-
-class SettingsGroup(models.Model):
-    key = models.CharField(max_length=255, unique=True)
-    name = models.CharField(max_length=255)
-    description = models.TextField(null=True, blank=True)
-
-    objects = SettingsGroupsManager()
+from .hydrators import dehydrate_value, hydrate_value
 
 
 
 
 class SettingsManager(models.Manager):
 class SettingsManager(models.Manager):
@@ -42,37 +19,22 @@ class SettingsManager(models.Manager):
 
 
 
 
 class Setting(models.Model):
 class Setting(models.Model):
-    # DELETE
-    group = models.ForeignKey(SettingsGroup, on_delete=models.CASCADE)
     setting = models.CharField(max_length=255, unique=True)
     setting = models.CharField(max_length=255, unique=True)
-    # DELETE
-    name = models.CharField(max_length=255)
-    # DELETE
-    description = models.TextField(null=True, blank=True)
-    # DELETE
-    legend = models.CharField(max_length=255, null=True, blank=True)
-    # DELETE
-    order = models.IntegerField(default=0, db_index=True)
     dry_value = models.TextField(null=True, blank=True)
     dry_value = models.TextField(null=True, blank=True)
-    default_value = models.TextField(null=True, blank=True)
     python_type = models.CharField(max_length=255, default="string")
     python_type = models.CharField(max_length=255, default="string")
     is_public = models.BooleanField(default=False)
     is_public = models.BooleanField(default=False)
     is_lazy = models.BooleanField(default=False)
     is_lazy = models.BooleanField(default=False)
-    # DELETE
-    form_field = models.CharField(max_length=255, default="text")
-    # DELETE
-    field_extra = JSONField()
 
 
     objects = SettingsManager()
     objects = SettingsManager()
 
 
     @property
     @property
     def value(self):
     def value(self):
-        return utils.get_setting_value(self)
+        return hydrate_value(self.python_type, self.dry_value)
 
 
     @value.setter
     @value.setter
     def value(self, new_value):
     def value(self, new_value):
-        return utils.set_setting_value(self, new_value)
-
-    @property
-    def has_custom_value(self):
-        return utils.has_custom_value(self)
+        if new_value is not None:
+            self.dry_value = dehydrate_value(self.python_type, new_value)
+        else:
+            self.dry_value = None
+        return new_value

+ 9 - 36
misago/conf/tests/conftest.py

@@ -1,56 +1,29 @@
 import pytest
 import pytest
 
 
-from ..models import Setting, SettingsGroup
+from ..models import Setting
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def settings_group(db):
-    return SettingsGroup.objects.create(key="test", name="Test")
-
-
-@pytest.fixture
-def lazy_setting(settings_group):
+def lazy_setting(db):
     return Setting.objects.create(
     return Setting.objects.create(
-        group=settings_group,
-        setting="lazy_setting",
-        name="Lazy setting",
-        dry_value="Hello",
-        is_lazy=True,
-        field_extra={},
+        setting="lazy_setting", dry_value="Hello", is_lazy=True
     )
     )
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def lazy_setting_without_value(settings_group):
-    return Setting.objects.create(
-        group=settings_group,
-        setting="lazy_setting",
-        name="Lazy setting",
-        dry_value="",
-        is_lazy=True,
-        field_extra={},
-    )
+def lazy_setting_without_value(db):
+    return Setting.objects.create(setting="lazy_setting", dry_value="", is_lazy=True)
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def private_setting(settings_group):
+def private_setting(db):
     return Setting.objects.create(
     return Setting.objects.create(
-        group=settings_group,
-        setting="private_setting",
-        name="Private setting",
-        dry_value="Hello",
-        is_public=False,
-        field_extra={},
+        setting="private_setting", dry_value="Hello", is_public=False
     )
     )
 
 
 
 
 @pytest.fixture
 @pytest.fixture
-def public_setting(settings_group):
+def public_setting(db):
     return Setting.objects.create(
     return Setting.objects.create(
-        group=settings_group,
-        setting="public_setting",
-        name="Public setting",
-        dry_value="Hello",
-        is_public=True,
-        field_extra={},
+        setting="public_setting", dry_value="Hello", is_public=True
     )
     )

+ 53 - 84
misago/conf/tests/test_hydrators.py

@@ -1,112 +1,81 @@
-from django.test import TestCase
+import pytest
 
 
 from ..hydrators import dehydrate_value, hydrate_value
 from ..hydrators import dehydrate_value, hydrate_value
-from ..models import Setting
 
 
 
 
-class HydratorsTests(TestCase):
-    def test_hydrate_dehydrate_string(self):
-        """string value is correctly hydrated and dehydrated"""
-        wet_value = "Ni!"
-        dry_value = dehydrate_value("string", wet_value)
-        self.assertEqual(hydrate_value("string", dry_value), wet_value)
+def test_string_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("string", dehydrate_value("string", "test")) == "test"
 
 
-    def test_hydrate_dehydrate_bool(self):
-        """bool values are correctly hydrated and dehydrated"""
-        wet_value = True
-        dry_value = dehydrate_value("bool", wet_value)
-        self.assertEqual(hydrate_value("bool", dry_value), wet_value)
 
 
-        wet_value = False
-        dry_value = dehydrate_value("bool", wet_value)
-        self.assertEqual(hydrate_value("bool", dry_value), wet_value)
+def test_int_value_is_dehydrated_to_string():
+    assert dehydrate_value("string", 123) == "123"
 
 
-    def test_hydrate_dehydrate_int(self):
-        """int value is correctly hydrated and dehydrated"""
-        wet_value = 9001
-        dry_value = dehydrate_value("int", wet_value)
-        self.assertEqual(hydrate_value("int", dry_value), wet_value)
 
 
-    def test_hydrate_dehydrate_list(self):
-        """list is correctly hydrated and dehydrated"""
-        wet_value = ["foxtrot", "uniform", "hotel"]
-        dry_value = dehydrate_value("list", wet_value)
-        self.assertEqual(hydrate_value("list", dry_value), wet_value)
+def test_bool_false_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("bool", dehydrate_value("bool", False)) is False
 
 
-    def test_hydrate_dehydrate_empty_list(self):
-        """empty list is correctly hydrated and dehydrated"""
-        wet_value = []
-        dry_value = dehydrate_value("list", wet_value)
-        self.assertEqual(hydrate_value("list", dry_value), wet_value)
 
 
-    def test_value_error(self):
-        """unsupported type raises ValueError"""
-        with self.assertRaises(ValueError):
-            hydrate_value("eric", None)
+def test_bool_true_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("bool", dehydrate_value("bool", True)) is True
 
 
-        with self.assertRaises(ValueError):
-            dehydrate_value("eric", None)
 
 
+def test_bool_none_value_can_be_dehydrated_and_hydrated_back_to_false():
+    assert hydrate_value("bool", dehydrate_value("bool", None)) is False
 
 
-class HydratorsModelTests(TestCase):
-    def test_hydrate_dehydrate_string(self):
-        """string value is correctly hydrated and dehydrated in model"""
-        setting = Setting(python_type="string")
 
 
-        wet_value = "Lorem Ipsum"
-        dry_value = dehydrate_value(setting.python_type, wet_value)
+def test_int_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("int", dehydrate_value("int", 123)) == 123
 
 
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
 
 
-    def test_hydrate_dehydrate_bool(self):
-        """bool values are correctly hydrated and dehydrated in model"""
-        setting = Setting(python_type="bool")
+def test_empty_int_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("int", dehydrate_value("int", 0)) == 0
 
 
-        wet_value = True
-        dry_value = dehydrate_value(setting.python_type, wet_value)
 
 
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
+def test_none_int_value_is_dehydrated_to_zero_string():
+    assert dehydrate_value("int", None) == "0"
 
 
-        wet_value = False
-        dry_value = dehydrate_value(setting.python_type, wet_value)
 
 
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
+def test_none_int_value_is_hydrated_to_zero():
+    assert hydrate_value("int", None) == 0
 
 
-    def test_hydrate_dehydrate_int(self):
-        """int value is correctly hydrated and dehydrated in model"""
-        setting = Setting(python_type="int")
 
 
-        wet_value = 9001
-        dry_value = dehydrate_value(setting.python_type, wet_value)
+def test_empty_int_value_is_hydrated_to_zero():
+    assert hydrate_value("int", "") == 0
 
 
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
 
 
-    def test_hydrate_dehydrate_list(self):
-        """list is correctly hydrated and dehydrated in model"""
-        setting = Setting(python_type="list")
+def test_list_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("list", dehydrate_value("list", ["a", "b"])) == ["a", "b"]
 
 
-        wet_value = ["Lorem", "Ipsum", "Dolor", "Met"]
-        dry_value = dehydrate_value(setting.python_type, wet_value)
 
 
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
+def test_single_item_list_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("list", dehydrate_value("list", ["a"])) == ["a"]
 
 
-    def test_hydrate_dehydrate_empty_list(self):
-        """empty list is correctly hydrated and dehydrated in model"""
-        setting = Setting(python_type="list")
 
 
-        wet_value = []
-        dry_value = dehydrate_value(setting.python_type, wet_value)
-
-        setting.value = wet_value
-        self.assertEqual(setting.value, wet_value)
-        self.assertEqual(setting.dry_value, dry_value)
+def test_empty_list_value_can_be_dehydrated_and_hydrated_back():
+    assert hydrate_value("list", dehydrate_value("list", [])) == []
+
+
+def test_none_list_value_can_be_dehydrated_and_hydrated_to_empty_list():
+    assert hydrate_value("list", dehydrate_value("list", None)) == []
+
+
+def test_empty_list_value_is_hydrated_to_empty_list():
+    assert hydrate_value("list", "") == []
+
+
+def test_none_list_value_is_hydrated_to_empty_list():
+    assert hydrate_value("list", None) == []
+
+
+def test_none_list_value_is_dehydrated_to_empty_string():
+    assert dehydrate_value("list", None) == ""
+
+
+def test_value_error_is_raised_on_unsupported_type_dehydration():
+    with pytest.raises(ValueError):
+        dehydrate_value("unsupported", None)
+
+
+def test_value_error_is_raised_on_unsupported_type_hydration():
+    with pytest.raises(ValueError):
+        hydrate_value("unsupported", None)

+ 0 - 86
misago/conf/tests/test_migrationutils.py

@@ -1,86 +0,0 @@
-from django.apps import apps
-from django.test import TestCase
-
-from .. import migrationutils
-from ..models import SettingsGroup
-
-
-class DBConfMigrationUtilsTests(TestCase):
-    def setUp(self):
-        self.test_group = {
-            "key": "test_group",
-            "name": "Test settings",
-            "description": "Those are test settings.",
-            "settings": [
-                {
-                    "setting": "fish_name",
-                    "name": "Fish's name",
-                    "value": "Eric",
-                    "field_extra": {"min_length": 2, "max_length": 255},
-                },
-                {
-                    "setting": "fish_license_no",
-                    "name": "Fish's license number",
-                    "default_value": "123-456",
-                    "field_extra": {"max_length": 255},
-                },
-            ],
-        }
-
-        migrationutils.migrate_settings_group(apps, self.test_group)
-        self.groups_count = SettingsGroup.objects.count()
-
-    def test_get_custom_group_and_settings(self):
-        """tests setup created settings group"""
-        custom_group = migrationutils.get_group(
-            apps.get_model("misago_conf", "SettingsGroup"), self.test_group["key"]
-        )
-
-        self.assertEqual(custom_group.key, self.test_group["key"])
-        self.assertEqual(custom_group.name, self.test_group["name"])
-        self.assertEqual(custom_group.description, self.test_group["description"])
-
-        custom_settings = migrationutils.get_custom_settings_values(custom_group)
-
-        self.assertEqual(custom_settings["fish_name"], "Eric")
-        self.assertTrue("fish_license_no" not in custom_settings)
-
-    def test_change_group_key(self):
-        """migrate_settings_group changed group key"""
-
-        new_group = {
-            "key": "new_test_group",
-            "name": "New test settings",
-            "description": "Those are updated test settings.",
-            "settings": [
-                {
-                    "setting": "fish_new_name",
-                    "name": "Fish's new name",
-                    "value": "Eric",
-                    "field_extra": {"min_length": 2, "max_length": 255},
-                },
-                {
-                    "setting": "fish_new_license_no",
-                    "name": "Fish's changed license number",
-                    "default_value": "123-456",
-                    "field_extra": {"max_length": 255},
-                },
-            ],
-        }
-
-        migrationutils.migrate_settings_group(
-            apps, new_group, old_group_key=self.test_group["key"]
-        )
-
-        db_group = migrationutils.get_group(
-            apps.get_model("misago_conf", "SettingsGroup"), new_group["key"]
-        )
-
-        self.assertEqual(SettingsGroup.objects.count(), self.groups_count)
-        self.assertEqual(db_group.key, new_group["key"])
-        self.assertEqual(db_group.name, new_group["name"])
-        self.assertEqual(db_group.description, new_group["description"])
-
-        for setting in new_group["settings"]:
-            db_setting = db_group.setting_set.get(setting=setting["setting"])
-            self.assertEqual(db_setting.name, setting["name"])

+ 18 - 0
misago/conf/tests/test_model_value_prop.py

@@ -0,0 +1,18 @@
+from ..models import Setting
+
+
+def test_setting_value_is_hydrated_by_getter(db):
+    setting = Setting(python_type="list", dry_value="lorem,ipsum")
+    assert setting.value == ["lorem", "ipsum"]
+
+
+def test_setting_value_is_dehydrated_by_setter(db):
+    setting = Setting(python_type="list")
+    setting.value = ["lorem", "ipsum"]
+    assert setting.dry_value == "lorem,ipsum"
+
+
+def test_setting_value_is_set_to_none(db):
+    setting = Setting(python_type="list", dry_value="lorem,ipsum")
+    setting.value = None
+    assert setting.dry_value is None

+ 0 - 47
misago/conf/tests/test_models.py

@@ -1,47 +0,0 @@
-from django.test import TestCase
-
-from ..models import Setting
-
-
-class SettingModelTests(TestCase):
-    def test_real_value(self):
-        """setting returns real value correctyly"""
-        setting_model = Setting(python_type="list", dry_value="")
-        self.assertEqual(setting_model.value, [])
-
-        setting_model = Setting(python_type="list", dry_value="Arthur,Lancelot,Patsy")
-        self.assertEqual(setting_model.value, ["Arthur", "Lancelot", "Patsy"])
-
-        setting_model = Setting(python_type="list", default_value="Arthur,Patsy")
-        self.assertEqual(setting_model.value, ["Arthur", "Patsy"])
-
-        setting_model = Setting(
-            python_type="list",
-            dry_value="Arthur,Robin,Patsy",
-            default_value="Arthur,Patsy",
-        )
-        self.assertEqual(setting_model.value, ["Arthur", "Robin", "Patsy"])
-
-    def test_set_value(self):
-        """setting sets value correctyly"""
-        setting_model = Setting(python_type="int", dry_value="42", default_value="9001")
-
-        setting_model.value = 3000
-        self.assertEqual(setting_model.value, 3000)
-        self.assertEqual(setting_model.dry_value, "3000")
-
-        setting_model.value = None
-        self.assertEqual(setting_model.value, 9001)
-        self.assertEqual(setting_model.dry_value, None)
-
-    def test_field_extra(self):
-        """field extra is set correctly"""
-        setting_model = Setting()
-
-        test_extra = {}
-        setting_model.field_extra = test_extra
-        self.assertEqual(setting_model.field_extra, test_extra)
-
-        test_extra = {"min_lenght": 5, "max_length": 12}
-        setting_model.field_extra = test_extra
-        self.assertEqual(setting_model.field_extra, test_extra)

+ 0 - 19
misago/conf/utils.py

@@ -1,19 +0,0 @@
-from . import hydrators
-
-
-def get_setting_value(setting):
-    if not setting.dry_value and setting.default_value:
-        return hydrators.hydrate_value(setting.python_type, setting.default_value)
-    return hydrators.hydrate_value(setting.python_type, setting.dry_value)
-
-
-def set_setting_value(setting, new_value):
-    if new_value is not None:
-        setting.dry_value = hydrators.dehydrate_value(setting.python_type, new_value)
-    else:
-        setting.dry_value = None
-    return setting.value
-
-
-def has_custom_value(setting):
-    return setting.dry_value and setting.dry_value != setting.default_value

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

@@ -1,83 +1,8 @@
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def create_basic_settings_group(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "basic",
-            "name": _("Basic forum settings"),
-            "description": _(
-                "Those settings control most basic properties "
-                "of your forum like its name or description."
-            ),
-            "settings": [
-                {
-                    "setting": "forum_name",
-                    "name": _("Forum name"),
-                    "legend": _("General"),
-                    "value": "Misago",
-                    "field_extra": {"min_length": 2, "max_length": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "forum_index_title",
-                    "name": _("Index title"),
-                    "description": _(
-                        "You may set custon title on forum index by typing it here."
-                    ),
-                    "legend": _("Forum index"),
-                    "field_extra": {"max_length": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "forum_index_meta_description",
-                    "name": _("Meta Description"),
-                    "description": _(
-                        "Short description of your forum for internet crawlers."
-                    ),
-                    "field_extra": {"max_length": 255},
-                },
-                {
-                    "setting": "forum_branding_display",
-                    "name": _("Display branding"),
-                    "description": _("Switch branding in forum's navbar."),
-                    "legend": _("Branding"),
-                    "value": True,
-                    "python_type": "bool",
-                    "form_field": "yesno",
-                    "is_public": True,
-                },
-                {
-                    "setting": "forum_branding_text",
-                    "name": _("Branding text"),
-                    "description": _(
-                        "Optional text displayed besides brand image in navbar."
-                    ),
-                    "value": "Misago",
-                    "field_extra": {"max_length": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "email_footer",
-                    "name": _("E-mails footer"),
-                    "description": _(
-                        "Optional short message included at the end of e-mails sent by forum."
-                    ),
-                    "legend": _("Forum e-mails"),
-                    "field_extra": {"max_length": 255},
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_core", "0001_initial"), ("misago_conf", "0001_initial")]
     dependencies = [("misago_core", "0001_initial"), ("misago_conf", "0001_initial")]
 
 
-    operations = [migrations.RunPython(create_basic_settings_group)]
+    operations = []

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

@@ -1,100 +1,5 @@
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def create_legal_settings_group(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "legal",
-            "name": _("Legal information"),
-            "description": _(
-                "Those settings allow you to set forum terms of service and privacy policy."
-            ),
-            "settings": [
-                {
-                    "setting": "terms_of_service_title",
-                    "name": _("Terms title"),
-                    "legend": _("Terms of Service"),
-                    "description": _("Leave this field empty to use default title."),
-                    "value": "",
-                    "field_extra": {"max_length": 255, "required": False},
-                    "is_public": True,
-                },
-                {
-                    "setting": "terms_of_service_link",
-                    "name": _("Terms link"),
-                    "description": _(
-                        "If terms of service are located on other page, enter there its link."
-                    ),
-                    "value": "",
-                    "field_extra": {"max_length": 255, "required": False},
-                    "is_public": True,
-                },
-                {
-                    "setting": "terms_of_service",
-                    "name": _("Terms contents"),
-                    "description": _(
-                        "Your forums can have custom terms of "
-                        "service page. To create it, write or "
-                        "paste here its contents. Full Misago "
-                        "markup is available for formatting."
-                    ),
-                    "value": "",
-                    "form_field": "textarea",
-                    "field_extra": {"max_length": 128000, "required": False, "rows": 8},
-                    "is_public": True,
-                    "is_lazy": True,
-                },
-                {
-                    "setting": "privacy_policy_title",
-                    "name": _("Policy title"),
-                    "legend": _("Privacy policy"),
-                    "description": _("Leave this field empty to use default title."),
-                    "value": "",
-                    "field_extra": {"max_length": 255, "required": False},
-                    "is_public": True,
-                },
-                {
-                    "setting": "privacy_policy_link",
-                    "name": _("Policy link"),
-                    "description": _(
-                        "If privacy policy is located on other page, enter there its link."
-                    ),
-                    "value": "",
-                    "field_extra": {"max_length": 255, "required": False},
-                    "is_public": True,
-                },
-                {
-                    "setting": "privacy_policy",
-                    "name": _("Policy contents"),
-                    "description": _(
-                        "Your forums can have custom privacy "
-                        "policy page. To create it, write or "
-                        "paste here its contents. Full Misago "
-                        "markup is available for formatting."
-                    ),
-                    "value": "",
-                    "form_field": "textarea",
-                    "field_extra": {"max_length": 128000, "required": False, "rows": 8},
-                    "is_public": True,
-                    "is_lazy": True,
-                },
-                {
-                    "setting": "forum_footnote",
-                    "name": _("Footnote"),
-                    "description": _("Short message displayed in forum footer."),
-                    "legend": _("Forum footer"),
-                    "field_extra": {"max_length": 300},
-                    "is_public": True,
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
@@ -102,4 +7,4 @@ class Migration(migrations.Migration):
 
 
     dependencies = [("misago_conf", "0001_initial")]
     dependencies = [("misago_conf", "0001_initial")]
 
 
-    operations = [migrations.RunPython(create_legal_settings_group)]
+    operations = []

+ 1 - 71
misago/legal/migrations/0003_create_agreements_from_settings.py

@@ -1,79 +1,9 @@
 # Generated by Django 1.11.15 on 2018-08-16 14:22
 # Generated by Django 1.11.15 on 2018-08-16 14:22
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-from ..models import Agreement as MisagoAgreement
-
-_ = lambda s: s
-
-
-LEGAL_SETTINGS = [
-    "terms_of_service_title",
-    "terms_of_service_link",
-    "terms_of_service",
-    "privacy_policy_title",
-    "privacy_policy_link",
-    "privacy_policy",
-]
-
-
-def create_legal_settings_group(apps, schema_editor):
-    Agreement = apps.get_model("misago_legal", "Agreement")
-    Setting = apps.get_model("misago_conf", "Setting")
-
-    legal_conf = {}
-    for setting in Setting.objects.filter(setting__in=LEGAL_SETTINGS):
-        legal_conf[setting.setting] = setting.dry_value
-
-    if legal_conf["terms_of_service"] or legal_conf["terms_of_service_link"]:
-        Agreement.objects.create(
-            type=MisagoAgreement.TYPE_TOS,
-            title=legal_conf["terms_of_service_title"],
-            link=legal_conf["terms_of_service_link"],
-            text=legal_conf["terms_of_service"],
-            is_active=True,
-        )
-
-    if legal_conf["privacy_policy"] or legal_conf["privacy_policy_link"]:
-        Agreement.objects.create(
-            type=MisagoAgreement.TYPE_PRIVACY,
-            title=legal_conf["privacy_policy_title"],
-            link=legal_conf["privacy_policy_link"],
-            text=legal_conf["privacy_policy"],
-            is_active=True,
-        )
-
-    MisagoAgreement.objects.invalidate_cache()
-
-
-def delete_deprecated_settings(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "legal",
-            "name": _("Legal information"),
-            "description": _(
-                "Those settings allow you to set additional legal information for your forum."
-            ),
-            "settings": [
-                {
-                    "setting": "forum_footnote",
-                    "name": _("Footnote"),
-                    "description": _("Short message displayed in forum footer."),
-                    "legend": _("Forum footer"),
-                    "field_extra": {"max_length": 300},
-                    "is_public": True,
-                }
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_legal", "0002_agreement_useragreement")]
     dependencies = [("misago_legal", "0002_agreement_useragreement")]
 
 
-    operations = [
-        migrations.RunPython(create_legal_settings_group),
-        migrations.RunPython(delete_deprecated_settings),
-    ]
+    operations = []

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

@@ -1,66 +1,8 @@
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def create_threads_settings_group(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "threads",
-            "name": _("Threads"),
-            "description": _("Those settings control threads and posts."),
-            "settings": [
-                {
-                    "setting": "thread_title_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed thread title length."),
-                    "legend": _("Thread titles"),
-                    "python_type": "int",
-                    "value": 5,
-                    "field_extra": {"min_value": 2, "max_value": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "thread_title_length_max",
-                    "name": _("Maximum length"),
-                    "description": _("Maximum allowed thread length."),
-                    "python_type": "int",
-                    "value": 90,
-                    "field_extra": {"min_value": 2, "max_value": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "post_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed user post length."),
-                    "legend": _("Posts"),
-                    "python_type": "int",
-                    "value": 5,
-                    "field_extra": {"min_value": 1},
-                    "is_public": True,
-                },
-                {
-                    "setting": "post_length_max",
-                    "name": _("Maximum length"),
-                    "description": _(
-                        "Maximum allowed user post length. Enter zero to disable. "
-                        "Longer posts are more costful to parse and index."
-                    ),
-                    "python_type": "int",
-                    "value": 60000,
-                    "field_extra": {"min_value": 0},
-                    "is_public": True,
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_threads", "0001_initial"), ("misago_conf", "0001_initial")]
     dependencies = [("misago_threads", "0001_initial"), ("misago_conf", "0001_initial")]
 
 
-    operations = [migrations.RunPython(create_threads_settings_group)]
+    operations = []

+ 1 - 59
misago/threads/migrations/0004_update_settings.py

@@ -1,66 +1,8 @@
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def update_threads_settings(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "threads",
-            "name": _("Threads"),
-            "description": _("Those settings control threads and posts."),
-            "settings": [
-                {
-                    "setting": "thread_title_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed thread title length."),
-                    "legend": _("Thread titles"),
-                    "python_type": "int",
-                    "default_value": 5,
-                    "field_extra": {"min_value": 2, "max_value": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "thread_title_length_max",
-                    "name": _("Maximum length"),
-                    "description": _("Maximum allowed thread length."),
-                    "python_type": "int",
-                    "default_value": 90,
-                    "field_extra": {"min_value": 2, "max_value": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "post_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed user post length."),
-                    "legend": _("Posts"),
-                    "python_type": "int",
-                    "default_value": 5,
-                    "field_extra": {"min_value": 1},
-                    "is_public": True,
-                },
-                {
-                    "setting": "post_length_max",
-                    "name": _("Maximum length"),
-                    "description": _(
-                        "Maximum allowed user post length. Enter zero to disable. "
-                        "Longer posts are more costful to parse and index."
-                    ),
-                    "python_type": "int",
-                    "default_value": 60000,
-                    "field_extra": {"min_value": 0},
-                    "is_public": True,
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_threads", "0003_attachment_types")]
     dependencies = [("misago_threads", "0003_attachment_types")]
 
 
-    operations = [migrations.RunPython(update_threads_settings)]
+    operations = []

+ 1 - 235
misago/users/migrations/0002_users_settings.py

@@ -1,242 +1,8 @@
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def create_users_settings_group(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "users",
-            "name": _("Users"),
-            "description": _(
-                "Those settings control user accounts default behaviour and features availability."
-            ),
-            "settings": [
-                {
-                    "setting": "account_activation",
-                    "name": _("New accounts activation"),
-                    "legend": _("New accounts"),
-                    "value": "none",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("none", _("No activation required")),
-                            ("user", _("Activation token sent to User")),
-                            ("admin", _("Activation by administrator")),
-                            ("closed", _("Don't allow new registrations")),
-                        ]
-                    },
-                    "is_public": True,
-                },
-                {
-                    "setting": "username_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed username length."),
-                    "legend": _("User names"),
-                    "python_type": "int",
-                    "value": 3,
-                    "field_extra": {"min_value": 2, "max_value": 20},
-                    "is_public": True,
-                },
-                {
-                    "setting": "username_length_max",
-                    "name": _("Maximum length"),
-                    "description": _("Maximum allowed username length."),
-                    "python_type": "int",
-                    "value": 14,
-                    "field_extra": {"min_value": 2, "max_value": 20},
-                    "is_public": True,
-                },
-                {
-                    "setting": "password_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed user password length."),
-                    "legend": _("Passwords"),
-                    "python_type": "int",
-                    "value": 5,
-                    "field_extra": {"min_value": 2, "max_value": 255},
-                    "is_public": True,
-                },
-                {
-                    "setting": "allow_custom_avatars",
-                    "name": _("Allow custom avatars"),
-                    "legend": _("Avatars"),
-                    "description": _(
-                        "Turning this option off will forbid "
-                        "forum users from using avatars from "
-                        "outside forums. Good for forums "
-                        "adressed at young users."
-                    ),
-                    "python_type": "bool",
-                    "value": True,
-                    "form_field": "yesno",
-                },
-                {
-                    "setting": "default_avatar",
-                    "name": _("Default avatar"),
-                    "value": "gravatar",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("dynamic", _("Individual")),
-                            ("gravatar", _("Gravatar")),
-                            ("gallery", _("Random avatar from gallery")),
-                        ]
-                    },
-                },
-                {
-                    "setting": "default_gravatar_fallback",
-                    "name": _("Fallback for default gravatar"),
-                    "description": _(
-                        "Select which avatar to use when user has no "
-                        "gravatar associated with his e-mail address."
-                    ),
-                    "value": "dynamic",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("dynamic", _("Individual")),
-                            ("gallery", _("Random avatar from gallery")),
-                        ]
-                    },
-                },
-                {
-                    "setting": "avatar_upload_limit",
-                    "name": _("Maximum size of uploaded avatar"),
-                    "description": _(
-                        "Enter maximum allowed file size (in KB) for avatar uploads."
-                    ),
-                    "python_type": "int",
-                    "value": 1536,
-                    "field_extra": {"min_value": 0},
-                    "is_public": True,
-                },
-                {
-                    "setting": "signature_length_max",
-                    "name": _("Maximum length"),
-                    "legend": _("Signatures"),
-                    "description": _("Maximum allowed signature length."),
-                    "python_type": "int",
-                    "value": 256,
-                    "field_extra": {"min_value": 10, "max_value": 5000},
-                    "is_public": True,
-                },
-                {
-                    "setting": "subscribe_start",
-                    "name": _("Started threads"),
-                    "legend": _("Default subscriptions settings"),
-                    "value": "watch_email",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("no", _("Don't watch")),
-                            ("watch", _("Put on watched threads list")),
-                            (
-                                "watch_email",
-                                _(
-                                    "Put on watched threads "
-                                    "list and e-mail user when "
-                                    "somebody replies"
-                                ),
-                            ),
-                        ]
-                    },
-                },
-                {
-                    "setting": "subscribe_reply",
-                    "name": _("Replied threads"),
-                    "value": "watch_email",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("no", _("Don't watch")),
-                            ("watch", _("Put on watched threads list")),
-                            (
-                                "watch_email",
-                                _(
-                                    "Put on watched threads "
-                                    "list and e-mail user when "
-                                    "somebody replies"
-                                ),
-                            ),
-                        ]
-                    },
-                },
-            ],
-        },
-    )
-
-    migrate_settings_group(
-        apps,
-        {
-            "key": "captcha",
-            "name": _("CAPTCHA"),
-            "description": _(
-                "Those settings allow you to combat automatic registrations on your forum."
-            ),
-            "settings": [
-                {
-                    "setting": "captcha_type",
-                    "name": _("Select CAPTCHA type"),
-                    "legend": _("CAPTCHA type"),
-                    "value": "no",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("no", _("No CAPTCHA")),
-                            ("re", _("reCaptcha")),
-                            ("qa", _("Question and answer")),
-                        ]
-                    },
-                    "is_public": True,
-                },
-                {
-                    "setting": "recaptcha_site_key",
-                    "name": _("Site key"),
-                    "legend": _("reCAPTCHA"),
-                    "value": "",
-                    "field_extra": {"required": False, "max_length": 100},
-                    "is_public": True,
-                },
-                {
-                    "setting": "recaptcha_secret_key",
-                    "name": _("Secret key"),
-                    "value": "",
-                    "field_extra": {"required": False, "max_length": 100},
-                },
-                {
-                    "setting": "qa_question",
-                    "name": _("Test question"),
-                    "legend": _("Question and answer"),
-                    "value": "",
-                    "field_extra": {"required": False, "max_length": 250},
-                },
-                {
-                    "setting": "qa_help_text",
-                    "name": _("Question help text"),
-                    "value": "",
-                    "field_extra": {"required": False, "max_length": 250},
-                },
-                {
-                    "setting": "qa_answers",
-                    "name": _("Valid answers"),
-                    "description": _(
-                        "Enter each answer in new line. Answers are case-insensitive."
-                    ),
-                    "value": "",
-                    "form_field": "textarea",
-                    "field_extra": {"rows": 4, "required": False, "max_length": 250},
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_users", "0001_initial"), ("misago_conf", "0001_initial")]
     dependencies = [("misago_users", "0001_initial"), ("misago_conf", "0001_initial")]
 
 
-    operations = [migrations.RunPython(create_users_settings_group)]
+    operations = []

+ 1 - 158
misago/users/migrations/0006_update_settings.py

@@ -1,166 +1,9 @@
 # Generated by Django 1.10.5 on 2017-02-05 14:34
 # Generated by Django 1.10.5 on 2017-02-05 14:34
 from django.db import migrations
 from django.db import migrations
 
 
-from ...conf.migrationutils import migrate_settings_group
-
-_ = lambda s: s
-
-
-def update_users_settings(apps, schema_editor):
-    migrate_settings_group(
-        apps,
-        {
-            "key": "users",
-            "name": _("Users"),
-            "description": _(
-                "Those settings control user accounts default behaviour and features availability."
-            ),
-            "settings": [
-                {
-                    "setting": "account_activation",
-                    "name": _("New accounts activation"),
-                    "legend": _("New accounts"),
-                    "value": "none",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("none", _("No activation required")),
-                            ("user", _("Activation token sent to User")),
-                            ("admin", _("Activation by administrator")),
-                            ("closed", _("Don't allow new registrations")),
-                        ]
-                    },
-                    "is_public": True,
-                },
-                {
-                    "setting": "username_length_min",
-                    "name": _("Minimum length"),
-                    "description": _("Minimum allowed username length."),
-                    "legend": _("User names"),
-                    "python_type": "int",
-                    "default_value": 3,
-                    "field_extra": {"min_value": 2, "max_value": 20},
-                },
-                {
-                    "setting": "username_length_max",
-                    "name": _("Maximum length"),
-                    "description": _("Maximum allowed username length."),
-                    "python_type": "int",
-                    "default_value": 14,
-                    "field_extra": {"min_value": 2, "max_value": 20},
-                },
-                {
-                    "setting": "allow_custom_avatars",
-                    "name": _("Allow custom avatars"),
-                    "legend": _("Avatars"),
-                    "description": _(
-                        "Turning this option off will forbid "
-                        "forum users from using avatars from "
-                        "outside forums. Good for forums "
-                        "adressed at young users."
-                    ),
-                    "python_type": "bool",
-                    "value": True,
-                    "form_field": "yesno",
-                },
-                {
-                    "setting": "default_avatar",
-                    "name": _("Default avatar"),
-                    "value": "gravatar",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("dynamic", _("Individual")),
-                            ("gravatar", _("Gravatar")),
-                            ("gallery", _("Random avatar from gallery")),
-                        ]
-                    },
-                },
-                {
-                    "setting": "default_gravatar_fallback",
-                    "name": _("Fallback for default gravatar"),
-                    "description": _(
-                        "Select which avatar to use when user has no "
-                        "gravatar associated with his e-mail address."
-                    ),
-                    "value": "dynamic",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("dynamic", _("Individual")),
-                            ("gallery", _("Random avatar from gallery")),
-                        ]
-                    },
-                },
-                {
-                    "setting": "avatar_upload_limit",
-                    "name": _("Maximum size of uploaded avatar"),
-                    "description": _(
-                        "Enter maximum allowed file size (in KB) for avatar uploads."
-                    ),
-                    "python_type": "int",
-                    "default_value": 1536,
-                    "field_extra": {"min_value": 0},
-                    "is_public": True,
-                },
-                {
-                    "setting": "signature_length_max",
-                    "name": _("Maximum length"),
-                    "legend": _("Signatures"),
-                    "description": _("Maximum allowed signature length."),
-                    "python_type": "int",
-                    "default_value": 256,
-                    "field_extra": {"min_value": 10, "max_value": 5000},
-                    "is_public": True,
-                },
-                {
-                    "setting": "subscribe_start",
-                    "name": _("Started threads"),
-                    "legend": _("Default subscriptions settings"),
-                    "value": "watch_email",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("no", _("Don't watch")),
-                            ("watch", _("Put on watched threads list")),
-                            (
-                                "watch_email",
-                                _(
-                                    "Put on watched threads "
-                                    "list and e-mail user when "
-                                    "somebody replies"
-                                ),
-                            ),
-                        ]
-                    },
-                },
-                {
-                    "setting": "subscribe_reply",
-                    "name": _("Replied threads"),
-                    "value": "watch_email",
-                    "form_field": "select",
-                    "field_extra": {
-                        "choices": [
-                            ("no", _("Don't watch")),
-                            ("watch", _("Put on watched threads list")),
-                            (
-                                "watch_email",
-                                _(
-                                    "Put on watched threads "
-                                    "list and e-mail user when "
-                                    "somebody replies"
-                                ),
-                            ),
-                        ]
-                    },
-                },
-            ],
-        },
-    )
-
 
 
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [("misago_users", "0005_dj_19_update")]
     dependencies = [("misago_users", "0005_dj_19_update")]
 
 
-    operations = [migrations.RunPython(update_users_settings)]
+    operations = []