diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index 1647161460720845a42f4b5896622048b32c07ae..aaee34bc1439fb045626ec07768f7b9555c67ef7 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/mailer.py b/aleksis/core/mailer.py index 6cd4c0c98cb0927e6aa72c0561a5327c10a67cad..e4dc6b4479ccd9e1ca5644108c3e0be7ed6ca4d5 100644 --- a/aleksis/core/mailer.py +++ b/aleksis/core/mailer.py @@ -1,10 +1,14 @@ from django.core.mail import send_mail from django.template.loader import render_to_string -SENDER_EMAIL = 'SchoolApps <infoplan@katharineum.de>' +from constance import config -def send_mail_with_template(title, receivers, plain_template, html_template, context={}, sender_email=SENDER_EMAIL): +mail_out = "{} <{}>".format(config.MAIL_OUT_NAME, + config.MAIL_OUT) if config.MAIL_OUT_NAME else config.MAIL_OUT + + +def send_mail_with_template(title, receivers, plain_template, html_template, context={}, mail_out=mail_out): msg_plain = render_to_string(plain_template, context) msg_html = render_to_string(html_template, context) @@ -12,7 +16,7 @@ def send_mail_with_template(title, receivers, plain_template, html_template, con send_mail( title, msg_plain, - sender_email, + mail_out, receivers, html_message=msg_html, ) diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py index c4ba24e2ab87e783a358721d90028e0a8c77b96a..a7662bc719438e5486d620bbb64127dfd347c7a5 100644 --- a/aleksis/core/menus.py +++ b/aleksis/core/menus.py @@ -129,8 +129,9 @@ MENUS = { }, ], "FOOTER_MENU_CORE": [ - {"name": _("Website"), "url": "https://aleksis.edugit.org/"}, + {"name": _("Website"), "url": "https://aleksis.edugit.io/"}, {"name": "Teckids e.V.", "url": "https://www.teckids.org/"}, + {"name": "Katharineum zu Lübeck", "url": "https://katharineum-zu-luebeck.de"} ], "DATA_MANAGEMENT_MENU": [], "SCHOOL_MANAGEMENT_MENU": [ diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 811cfaeb03c17393a95668abce719e4e7470544c..c3faec8d52d4b86df75202d5ab4ba3f0afca6d0d 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -5,8 +5,6 @@ from django.contrib.auth.models import User from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ - -import dbsettings from image_cropping import ImageCropField, ImageRatioField from phonenumber_field.modelfields import PhoneNumberField @@ -14,14 +12,6 @@ from .mailer import send_mail_with_template from .mixins import ExtensibleModel -class ThemeSettings(dbsettings.Group): - colour_primary = dbsettings.StringValue(default="#007bff") - colour_secondary = dbsettings.StringValue(default="#007bff") - - -theme_settings = ThemeSettings("Global theme settings") - - class School(models.Model): """A school that will have many other objects linked to it. AlekSIS has multi-tenant support by linking all objects to a school, diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 8fe5e7f5e8356e78b22127291ffc21f48c0a01d5..e92f3f847e5c1c9361f7803ca2c0ad2f4528b3a3 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,6 +119,7 @@ TEMPLATES = [ "django.contrib.messages.context_processors.messages", "maintenance_mode.context_processors.maintenance_mode", "settings_context_processor.context_processors.settings", + "constance.context_processors.config" ], }, }, @@ -231,6 +232,7 @@ NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "n YARN_INSTALLED_APPS = ["datatables", "highlight.js", "jquery", "manup", "materialize-css", "moment", "popper.js", "prop-types", "react", "react-dom", "material-design-icons-iconfont", "select2"] +YARN_INSTALLED_APPS = ["datatables", "jquery", "materialize-css", "material-design-icons-iconfont", "select2"] JS_URL = _settings.get("js_assets.url", STATIC_URL) JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules") @@ -249,7 +251,7 @@ ANY_JS = { SASS_PROCESSOR_AUTO_INCLUDE = False SASS_PROCESSOR_CUSTOM_FUNCTIONS = { "get-colour": "aleksis.core.util.sass_helpers.get_colour", - "get-theme-setting": "aleksis.core.util.sass_helpers.get_theme_setting", + "get-setting": "aleksis.core.util.sass_helpers.get_setting", } SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"), STATIC_ROOT] @@ -270,6 +272,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/static/theme.scss b/aleksis/core/static/theme.scss index 6cb2c96081bad9721be4e4a6bee006c698cf4ea2..18b344169fe115d9175d8ac76df80d7dc9c178ae 100644 --- a/aleksis/core/static/theme.scss +++ b/aleksis/core/static/theme.scss @@ -34,11 +34,11 @@ // 1. Colors // ========================================================================== -$primary-color: adjust-color(get-colour(get-theme-setting(colour_primary)), $alpha: 1); +$primary-color: adjust-color(get-colour(get-setting(COLOUR_PRIMARY)), $alpha: 1); $primary-color-light: lighten($primary-color, 15%) !default; $primary-color-dark: darken($primary-color, 15%) !default; -$secondary-color: adjust-color(get-colour(get-theme-setting(colour_secondary)), $alpha: 1); +$secondary-color: adjust-color(get-colour(get-setting(COLOUR_SECONDARY)), $alpha: 1); $success-color: color("green", "base") !default; $error-color: color("red", "base") !default; $link-color: color("light-blue", "darken-1") !default; diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index 854a16afb3c44f281c84bcd93b704b8c66faf3f4..e98f71587ed93f987ed710c0dd7721f86f21a407 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -124,28 +124,35 @@ </div> <div class="footer-copyright"> <div class="container"> - <span class="left"> - <a class="blue-text text-lighten-4" href="https://aleksis.edugit.org/"> - AlekSIS — The Free School Information System - </a> - © The AlekSIS Team @ - <a class="blue-text text-lighten-4" href="https://www.teckids.org"> - Teckids e.V. - </a> - and - <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de"> - Katharineum zu Lübeck - </a> - </span> - <span class="right"> - <span id="doit"></span> - - <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de/impressum/">Impressum</a> - · - <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de/datenschutzerklaerung/"> - Datenschutzerklärung - </a> - </span> + <div class="left"> + <a class="blue-text text-lighten-4" href="https://aleksis.edugit.org/"> + AlekSIS — The Free School Information System + </a> + © The AlekSIS Team @ + <a class="blue-text text-lighten-4" href="https://www.teckids.org"> + Teckids e.V. + </a> + and + <a class="blue-text text-lighten-4" href="https://katharineum-zu-luebeck.de"> + Katharineum zu Lübeck + </a> + </div> + <div class="right"> + <span id="doit"></span> + {% if DB_SETTINGS.footer.impress_url %} + <a class="blue-text text-lighten-4" href="{{ DB_SETTINGS.footer.impress_url }}"> + {% trans "Impress" %} + </a> + {% endif %} + {% if DB_SETTINGS.footer.privacy_url and DB_SETTINGS.footer.impress_url %} + · + {% endif %} + {% if DB_SETTINGS.footer.privacy_url %} + <a class="blue-text text-lighten-4" href="{{ DB_SETTINGS.footer.privacy_url }}"> + {% trans "Privacy Policy" %} + </a> + {% endif %} + </div> </div> </div> </footer> diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 2e3b0c98dbdd9831cc7d2e5a19d2511910c64e63..1368243ba3e4cc46daaadc50f2d2088afa06b55d 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -35,7 +35,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 2bb44b09c62a55219272d3021a78daf4aaa07b98..7c86f6337dc8d8dfaa89eb958737a3bf9a16c64f 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.models 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 cd62881947c361493e1275b5b46b8a8bec59cb7b..9d000b9d8897277815c951e71190cd424cd8328e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -161,6 +161,9 @@ optional = false python-versions = "*" version = "5.0.6" +[package.dependencies] +six = "*" + [[package]] category = "dev" description = "Code coverage measurement for Python" @@ -256,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" @@ -416,24 +430,23 @@ 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 = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior" -name = "django-pwa" +description = "Pickled object field for Django" +name = "django-picklefield" optional = false python-versions = "*" -version = "1.0.6" +version = "2.0" [package.dependencies] -django = ">=1.8" +Django = ">=1.11" + +[package.extras] +tests = ["tox"] [[package]] category = "main" @@ -1234,7 +1247,7 @@ category = "main" description = "YAML parser and emitter for Python" name = "pyyaml" optional = false -python-versions = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "5.2" [[package]] @@ -1658,7 +1671,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"] ldap = ["django-auth-ldap"] [metadata] -content-hash = "7c5cc3497e09ab1ef53c542aeba9787d5045524e6f1a9c299454f18aa4d592a6" +content-hash = "bca26513f4520039661e719a675e767eef8a19273d1430cd0810ea8a10ea40da" python-versions = "^3.7" [metadata.files] @@ -1782,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"}, @@ -1836,9 +1850,9 @@ 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-pwa = [ - {file = "django-pwa-1.0.6.tar.gz", hash = "sha256:b3f1ad0c5241fae4c7505423540de4db93077d7c88416ff6d2af545ffe209f34"}, - {file = "django_pwa-1.0.6-py3-none-any.whl", hash = "sha256:9306105fcb637ae16fea6527be4b147d45fd53db85efb1d4f61dfea6bf793e56"}, +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"}, @@ -2414,6 +2428,7 @@ urllib3 = [ ] wcwidth = [ {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"}, + {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"}, ] yubiotp = [ {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"}, diff --git a/pyproject.toml b/pyproject.toml index ea95aa00dd4b6873da4982ec10d5d8203693c190..03bf5cdc77f266f408b761cf8acba90e32d8cf99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,9 +48,9 @@ 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-pwa = "^1.0.6" +django-constance = {version = "^2.5.0", extras = ["database"]} [tool.poetry.extras] ldap = ["django-auth-ldap"]