From 4d144bd09af992530cae5ed76fd26de3314b984d Mon Sep 17 00:00:00 2001
From: Dominik George <nik@naturalnet.de>
Date: Sat, 4 Jan 2020 12:54:08 +0100
Subject: [PATCH] Replace django-dbsettings with django-constance

Rationale: django-constance is a bit better maintained and has some
features we need, e.g. injecting config into templates through a context
processor. Also, decoupling the settings definitions from import time
allows us to implement lazy access to the config, so we can later access
them in other variables insettings.py directly.
---
 aleksis/core/apps.py              |  5 ++--
 aleksis/core/db_settings.py       | 29 -------------------
 aleksis/core/mailer.py            |  7 +++--
 aleksis/core/processors.py        |  5 ----
 aleksis/core/settings.py          | 22 +++++++++++++--
 aleksis/core/urls.py              |  1 -
 aleksis/core/util/sass_helpers.py |  9 +++---
 poetry.lock                       | 46 ++++++++++++++++++++++++-------
 pyproject.toml                    |  2 +-
 9 files changed, 69 insertions(+), 57 deletions(-)
 delete mode 100644 aleksis/core/db_settings.py
 delete mode 100644 aleksis/core/processors.py

diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 164716146..aaee34bc1 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -1,5 +1,6 @@
 from django.apps import AppConfig, apps
-from django.db.models.signals import post_save
+
+from constance.signals import config_updated
 
 from .signals import clean_scss
 
@@ -10,4 +11,4 @@ class CoreConfig(AppConfig):
 
     def ready(self) -> None:
         clean_scss()
-        post_save.connect(clean_scss, sender=apps.get_model("dbsettings", "Setting"))
+        config_updated.connect(clean_scss)
diff --git a/aleksis/core/db_settings.py b/aleksis/core/db_settings.py
deleted file mode 100644
index 7e5c82293..000000000
--- a/aleksis/core/db_settings.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from django.utils.translation import ugettext_lazy as _
-
-import dbsettings
-
-
-class ThemeSettings(dbsettings.Group):
-    colour_primary = dbsettings.StringValue(_("Primary colour"), default="#007bff")
-    colour_secondary = dbsettings.StringValue(_("Secondary colour"), default="#007bff")
-
-
-class MailSettings(dbsettings.Group):
-    mail_out_name = dbsettings.StringValue(_("Mail out name"), default="AlekSIS", required=False)
-    mail_out = dbsettings.StringValue(_("Mail out address"), default="no-reply@aleksis.org")
-
-
-class FooterSettings(dbsettings.Group):
-    privacy_url = dbsettings.StringValue(_("Link to privacy policy"), default="")
-    impress_url = dbsettings.StringValue(_("Link to impress"), default="")
-
-
-theme_settings = ThemeSettings(_("Global theme settings"))
-mail_settings = MailSettings(_("Mail settings"))
-footer_settings = FooterSettings(_("Footer links"))
-
-db_settings = {
-    "theme": theme_settings,
-    "mail": mail_settings,
-    "footer": footer_settings
-}
diff --git a/aleksis/core/mailer.py b/aleksis/core/mailer.py
index 8ae90edb9..55e6d2b34 100644
--- a/aleksis/core/mailer.py
+++ b/aleksis/core/mailer.py
@@ -1,10 +1,11 @@
 from django.core.mail import send_mail
 from django.template.loader import render_to_string
 
-from aleksis.core.db_settings import mail_settings
+from constance import config
 
-mail_out = "{} <{}>".format(mail_settings.mail_out_name,
-                            mail_settings.mail_out) if mail_settings.mail_out_name != "" else mail_settings.mail_out
+
+mail_out = "{} <{}>".format(config.MAIL_OUT_NAME,
+                            config.MAIL_OUT) if config.MAIL_OUT_NAME else mail_settings.mail_out
 
 
 def send_mail_with_template(title, receivers, plain_template, html_template, context={}, mail_out=mail_out):
diff --git a/aleksis/core/processors.py b/aleksis/core/processors.py
deleted file mode 100644
index fcfe15407..000000000
--- a/aleksis/core/processors.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from aleksis.core.db_settings import db_settings
-
-
-def db_settings_processor(request):
-    return {"DB_SETTINGS": db_settings}
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index a27912a7a..f6df48676 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -53,7 +53,7 @@ INSTALLED_APPS = [
     "settings_context_processor",
     "sass_processor",
     "easyaudit",
-    "dbsettings",
+    "constance",
     "django_any_js",
     "django_yarnpkg",
     "django_tables2",
@@ -119,7 +119,7 @@ TEMPLATES = [
                 "django.contrib.messages.context_processors.messages",
                 "maintenance_mode.context_processors.maintenance_mode",
                 "settings_context_processor.context_processors.settings",
-                "aleksis.core.processors.db_settings_processor"
+                "constance.context_processors.config"
             ],
         },
     },
@@ -271,6 +271,24 @@ if _settings.get("mail.server.host", None):
 
 TEMPLATE_VISIBLE_SETTINGS = ["ADMINS", "DEBUG"]
 
+CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
+CONSTANCE_ADDITIONAL_FIELDS = {
+    "image_field": ["django.forms.ImageField", {}]
+}
+CONSTANCE_CONFIG = {
+    "COLOUR_PRIMARY": ("#007bff", "Primary colour"),
+    "COLOUR_SECONDARY": ("#007bff", "Secondary colour"),
+    "MAIL_OUT_NAME": ("AlekSIS", "Mail out name"),
+    "MAIL_OUT": ("aleksis@example.com", "Mail out address"),
+    "PRIVACY_URL": ("", "Link to privacy policy"),
+    "IMPRINT_URL": ("", "Link to imprint"),
+}
+CONSTANCE_CONFIG_FIELDSETS = {
+    "Theme settings": ("COLOUR_PRIMARY", "COLOUR_SECONDARY"),
+    "Mail settings": ("MAIL_OUT_NAME", "MAIL_OUT"),
+    "Footer settings": ("PRIVACY_URL", "IMPRINT_URL"),
+}
+
 MAINTENANCE_MODE = _settings.get("maintenance.enabled", None)
 MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = _settings.get(
     "maintenance.ignore_ips", _settings.get("maintenance.internal_ips", [])
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index a895fa051..17e71cc0c 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -33,7 +33,6 @@ urlpatterns = [
     path("impersonate/", include("impersonate.urls")),
     path("__i18n__/", include("django.conf.urls.i18n")),
     path("select2/", include("django_select2.urls")),
-    path("settings/", include("dbsettings.urls")),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
diff --git a/aleksis/core/util/sass_helpers.py b/aleksis/core/util/sass_helpers.py
index 0c883ffd4..7c86f6337 100644
--- a/aleksis/core/util/sass_helpers.py
+++ b/aleksis/core/util/sass_helpers.py
@@ -1,8 +1,9 @@
+from django.conf import settings
+
 from colour import web2hex
+from constance import config
 from sass import SassColor
 
-from aleksis.core.db_settings import theme_settings
-
 
 def get_colour(html_colour: str) -> SassColor:
     rgb = web2hex(html_colour, force_long=True)[1:]
@@ -11,5 +12,5 @@ def get_colour(html_colour: str) -> SassColor:
     return SassColor(r, g, b, 255)
 
 
-def get_theme_setting(setting: str) -> str:
-    return getattr(theme_settings, setting, "")
+def get_setting(setting: str) -> str:
+    return getattr(config, setting, "") or getattr(settings, setting, "")
diff --git a/poetry.lock b/poetry.lock
index 4c091fe70..9d000b9d8 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -259,11 +259,22 @@ Django = ">=1.8"
 
 [[package]]
 category = "main"
-description = "Application settings whose values can be updated while a project is up and running."
-name = "django-dbsettings"
+description = "Django live settings with pluggable backends, including Redis."
+name = "django-constance"
 optional = false
 python-versions = "*"
-version = "1.0.0"
+version = "2.5.0"
+
+[package.dependencies]
+six = "*"
+
+[package.dependencies.django-picklefield]
+optional = true
+version = "*"
+
+[package.extras]
+database = ["django-picklefield"]
+redis = ["redis"]
 
 [[package]]
 category = "main"
@@ -419,14 +430,24 @@ version = "3.0.1"
 Django = ">=1.11.3"
 babel = "*"
 
-[package.dependencies.phonenumbers]
-optional = true
-version = ">=7.0.2"
-
 [package.extras]
 phonenumbers = ["phonenumbers (>=7.0.2)"]
 phonenumberslite = ["phonenumberslite (>=7.0.2)"]
 
+[[package]]
+category = "main"
+description = "Pickled object field for Django"
+name = "django-picklefield"
+optional = false
+python-versions = "*"
+version = "2.0"
+
+[package.dependencies]
+Django = ">=1.11"
+
+[package.extras]
+tests = ["tox"]
+
 [[package]]
 category = "main"
 description = "SASS processor to compile SCSS files into *.css, while rendering, or offline."
@@ -1650,7 +1671,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"]
 ldap = ["django-auth-ldap"]
 
 [metadata]
-content-hash = "40917beab5394838573def3e2c8a6719f37040d826df74b3cdc9b52262ad0290"
+content-hash = "bca26513f4520039661e719a675e767eef8a19273d1430cd0810ea8a10ea40da"
 python-versions = "^3.7"
 
 [metadata.files]
@@ -1774,8 +1795,9 @@ django-bulk-update = [
     {file = "django-bulk-update-2.2.0.tar.gz", hash = "sha256:5ab7ce8a65eac26d19143cc189c0f041d5c03b9d1b290ca240dc4f3d6aaeb337"},
     {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"},
 ]
-django-dbsettings = [
-    {file = "django-dbsettings-1.0.0.tar.gz", hash = "sha256:42b04dffd3bc90d91718c822f1e0212d9368e8efe340f7ef09517b5fb1cf49f5"},
+django-constance = [
+    {file = "django-constance-2.5.0.tar.gz", hash = "sha256:c47db4abd5788584115db681c0e8fef8ff870d49af90aa359076f0833a537199"},
+    {file = "django_constance-2.5.0-py2.py3-none-any.whl", hash = "sha256:1b536d153c168ef548fea5bdcdea84afab83892e2163ec4e896557a3139effd7"},
 ]
 django-debug-toolbar = [
     {file = "django-debug-toolbar-2.1.tar.gz", hash = "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8"},
@@ -1828,6 +1850,10 @@ django-phonenumber-field = [
     {file = "django-phonenumber-field-3.0.1.tar.gz", hash = "sha256:794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"},
     {file = "django_phonenumber_field-3.0.1-py3-none-any.whl", hash = "sha256:1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e"},
 ]
+django-picklefield = [
+    {file = "django-picklefield-2.0.tar.gz", hash = "sha256:f1733a8db1b6046c0d7d738e785f9875aa3c198215de11993463a9339aa4ea24"},
+    {file = "django_picklefield-2.0-py2.py3-none-any.whl", hash = "sha256:9052f2dcf4882c683ce87b4356f29b4d014c0dad645b6906baf9f09571f52bc8"},
+]
 django-sass-processor = [
     {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index a1b518307..6f585b411 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,8 +48,8 @@ django_select2 = "^7.1"
 requests = "^2.22"
 django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] }
 django-yarnpkg = "^6.0"
-django-dbsettings = "^1.0.0"
 django-material = "^1.6.0"
+django-constance = {version = "^2.5.0", extras = ["database"]}
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
-- 
GitLab