From 18fe8aca9db3cc7c34cd046be10f3b8b8345bb42 Mon Sep 17 00:00:00 2001
From: Julian Leucker <leuckerj@gmail.com>
Date: Sat, 2 May 2020 14:33:29 +0200
Subject: [PATCH] Optimise icon usage. Closes #184

---
 aleksis/core/apps.py                        | 13 ++++++++--
 aleksis/core/preferences.py                 | 28 +++++++++++++++++++--
 aleksis/core/settings.py                    | 27 +++++++++++++-------
 aleksis/core/templates/core/base.html       |  4 +--
 aleksis/core/templates/core/base_print.html |  4 +--
 aleksis/core/templates/core/meta.html       |  6 ++---
 aleksis/core/util/core_helpers.py           | 20 ++++++++++++++-
 aleksis/core/views.py                       |  2 +-
 pyproject.toml                              |  1 +
 9 files changed, 81 insertions(+), 24 deletions(-)

diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index 3e2c5d5d0..492da4a3e 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -3,6 +3,7 @@ from typing import Any, List, Optional, Tuple
 import django.apps
 from django.contrib.auth.signals import user_logged_in
 from django.http import HttpRequest
+from django.utils.translation import gettext_lazy as _
 
 from dynamic_preferences.registries import preference_models
 
@@ -51,8 +52,16 @@ class CoreConfig(AppConfig):
         **kwargs,
     ) -> None:
         if section == "theme":
-            # Clean compiled SCSS to invalidate it after theme changes; recreated on request
-            clean_scss()
+            if name  in ("primary", "secondary"):
+                clean_scss()
+            elif name in ("favicon", "pwa_icon"):
+                from favicon.models import Favicon  # noqa
+
+                Favicon.on_site.update_or_create(title=name,
+                                                 defaults={
+                                                     "isFavicon": name == "favicon",
+                                                     "faviconImage": new_value,
+                                                 })
 
     def post_migrate(
         self,
diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py
index cd4255fb5..a5538eb96 100644
--- a/aleksis/core/preferences.py
+++ b/aleksis/core/preferences.py
@@ -1,9 +1,9 @@
 from django.conf import settings
-from django.forms import EmailField, URLField
+from django.forms import EmailField, URLField, ImageField
 from django.utils.translation import gettext_lazy as _
 
 from colorfield.widgets import ColorWidget
-from dynamic_preferences.types import BooleanPreference, ChoicePreference, StringPreference
+from dynamic_preferences.types import BooleanPreference, ChoicePreference, StringPreference, FilePreference
 from dynamic_preferences.preferences import Section
 from dynamic_preferences.registries import global_preferences_registry
 
@@ -56,6 +56,30 @@ class ColourSecondary(StringPreference):
     verbose_name = _("Secondary colour")
 
 
+@site_preferences_registry.register
+class Logo(FilePreference):
+    section = theme
+    field_class = ImageField
+    name = "logo"
+    verbose_name = _("Logo")
+
+
+@site_preferences_registry.register
+class Favicon(FilePreference):
+    section = theme
+    field_class = ImageField
+    name = "favicon"
+    verbose_name = _("Favicon")
+
+
+@site_preferences_registry.register
+class PWAIcon(FilePreference):
+    section = theme
+    field_class = ImageField
+    name = "pwa_icon"
+    verbose_name = _("PWA-Icon")
+
+
 @site_preferences_registry.register
 class MailOutName(StringPreference):
     section = mail
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 9cd0fc2fd..58ff7e2d6 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
 from dynaconf import LazySettings
 from easy_thumbnails.conf import Settings as thumbnail_settings
 
-from .util.core_helpers import get_app_packages, lazy_preference, merge_app_settings
+from .util.core_helpers import get_app_packages, lazy_preference, merge_app_settings, lazy_get_favicon_url
 
 ENVVAR_PREFIX_FOR_DYNACONF = "ALEKSIS"
 DIRS_FOR_DYNACONF = ["/etc/aleksis"]
@@ -93,6 +93,7 @@ INSTALLED_APPS = [
     "django_js_reverse",
     "colorfield",
     "django_bleach",
+    "favicon",
 ]
 
 merge_app_settings("INSTALLED_APPS", INSTALLED_APPS, True)
@@ -116,7 +117,7 @@ MIDDLEWARE = [
     "django.middleware.common.CommonMiddleware",
     "django.middleware.csrf.CsrfViewMiddleware",
     "django.contrib.auth.middleware.AuthenticationMiddleware",
-    "debug_toolbar.middleware.DebugToolbarMiddleware",
+    # "debug_toolbar.middleware.DebugToolbarMiddleware",
     "django_otp.middleware.OTPMiddleware",
     "impersonate.middleware.ImpersonateMiddleware",
     "django.contrib.messages.middleware.MessageMiddleware",
@@ -420,21 +421,29 @@ PWA_APP_BACKGROUND_COLOR = "#ffffff"
 PWA_APP_DISPLAY = "standalone"
 PWA_APP_ORIENTATION = "any"
 PWA_APP_ICONS = [  # three icons to upload dbsettings
-    {"src": STATIC_URL + "/icons/android_192.png", "sizes": "192x192"},
-    {"src": STATIC_URL + "/icons/android_512.png", "sizes": "512x512"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="android",
+                                 default=STATIC_URL + "icons/android_192.png"), "sizes": "192x192"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=512, rel="android",
+                                 default=STATIC_URL + "icons/android_512.png"), "sizes": "512x512"},
 ]
 PWA_APP_ICONS_APPLE = [
-    {"src": STATIC_URL + "/icons/apple_76.png", "sizes": "76x76"},
-    {"src": STATIC_URL + "/icons/apple_114.png", "sizes": "114x114"},
-    {"src": STATIC_URL + "/icons/apple_152.png", "sizes": "152x152"},
-    {"src": STATIC_URL + "/icons/apple_180.png", "sizes": "180x180"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
+                                 default=STATIC_URL + "icons/apple_76.png"), "sizes": "76x76"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
+                                 default=STATIC_URL + "icons/apple_114.png"), "sizes": "114x114"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
+                                 default=STATIC_URL + "icons/apple_152.png"), "sizes": "152x152"},
+    {"src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple",
+                                 default=STATIC_URL + "icons/apple_180.png"), "sizes": "180x180"},
 ]
 PWA_APP_SPLASH_SCREEN = [
     {
-        "src": STATIC_URL + "/icons/android_512.png",
+        "src": lazy_get_favicon_url(title="pwa_icon", size=192, rel="apple", default=STATIC_URL + "icons/apple_180.png"),
         "media": "(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)",
     }
 ]
+
+
 PWA_SERVICE_WORKER_PATH = os.path.join(STATIC_ROOT, "js", "serviceworker.js")
 
 SITE_ID = 1
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index f21d2efc8..b3fc8d66f 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -66,9 +66,7 @@
   <ul id="slide-out" class="sidenav sidenav-fixed">
     <li class="logo">
       <a id="logo-container" href="/" class="brand-logo">
-        <object type="image/svg+xml" data="{% static 'img/aleksis-icon.svg' %}" class="aleksis-logo-svg" id="sidenav-logo">
-          <img src="{% static 'img/aleksis-icon.png' %}" alt="AlekSIS icon" />
-        </object>
+        <img src="{{ request.site.preferences.theme__logo.url }}" alt="{{ request.site.preferences.general__title }} – Logo">
       </a>
     </li>
     {% has_perm 'core.search' user as search %}
diff --git a/aleksis/core/templates/core/base_print.html b/aleksis/core/templates/core/base_print.html
index abd42a1c7..f9b9d8450 100644
--- a/aleksis/core/templates/core/base_print.html
+++ b/aleksis/core/templates/core/base_print.html
@@ -1,4 +1,4 @@
-{% load static i18n any_js sass_tags cropping %}
+{% load static i18n any_js sass_tags %}
 
 <!DOCTYPE html>
 <html>
@@ -40,7 +40,7 @@
           <header>
             <div id="print-header" class="row">
               <div class="col s6 logo">
-                <img src="{% cropped_thumbnail SCHOOL 'logo_cropping' max_size="85x85" %}" alt="Logo" id="print-logo"/>
+                <img src="{{ request.site.preferences.theme__logo.url }}" alt="Logo" id="print-logo"/>
               </div>
               <div class="col s6 right-align">
                 <h5>{% block page_title %}{% endblock %}</h5>
diff --git a/aleksis/core/templates/core/meta.html b/aleksis/core/templates/core/meta.html
index 6a6087224..7b188462e 100644
--- a/aleksis/core/templates/core/meta.html
+++ b/aleksis/core/templates/core/meta.html
@@ -1,4 +1,4 @@
-{% load i18n static pwa %}
+{% load i18n static pwa favtags %}
 
 {# Basic meta #}
 <meta charset="utf-8">
@@ -7,9 +7,7 @@
 <meta name="description" content="{{ request.site.preferences.general__description }}">
 
 {# Favicons #}
-<link href="{% static "icons/favicon_16.png" %}" rel="icon" type="image/png" sizes="16x16">
-<link href="{% static "icons/favicon_32.png" %}" rel="icon" type="image/png" sizes="32x32">
-<link href="{% static "icons/favicon_48.png" %}" rel="icon" type="image/png" sizes="48x48">
+{% place_favicon %}
 
 {# PWA meta #}
 {% progressive_web_app_meta %}
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index f39b58f6f..c9c28deaf 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -4,7 +4,9 @@ from operator import itemgetter
 import os
 import pkgutil
 from importlib import import_module
-from typing import Any, Callable, Sequence, Union, List, Optional
+
+from typing import Any, Callable, List, Optional, Sequence, Union
+
 from uuid import uuid4
 
 from django.conf import settings
@@ -112,6 +114,22 @@ def lazy_preference(section: str, name: str) -> Callable[[str, str], Any]:
     return lazy(_get_preference, str)(section, name)
 
 
+def lazy_get_favicon_url(title: str, size: int, rel: str, default: Optional[str] = None) -> Callable[[str, str], Any]:
+    """ Lazily get the URL to a favicon image """
+
+    def _get_favicon_url(size: int, rel: str) -> Any:
+        from favicon.models import Favicon  # noqa
+
+        try:
+            favicon = Favicon.on_site.get(title=title)
+        except Favicon.DoesNotExist:
+            return default
+        else:
+            return favicon.get_favicon(size, rel).faviconImage.url
+
+    return lazy(_get_favicon_url, str)(size, rel)
+
+
 def is_impersonate(request: HttpRequest) -> bool:
     """ Check whether the user was impersonated by an admin """
 
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 2cdd5284e..bc82720fb 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -433,7 +433,7 @@ def preferences(request: HttpRequest, registry_name: str = "person", pk: Optiona
     form_class = preference_form_builder(form_class, instance=instance, section=section)
 
     if request.method == "POST":
-        form = form_class(request.POST)
+        form = form_class(request.POST, request.FILES or None)
         if form.is_valid():
             form.update_preferences()
             messages.success(request, _("The preferences have been saved successfully."))
diff --git a/pyproject.toml b/pyproject.toml
index 2bb92bfbb..b503eee1d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,6 +76,7 @@ django-dbbackup = "^3.3.0"
 spdx-license-list = "^0.4.0"
 license-expression = "^1.2"
 django-reversion = "^3.0.7"
+django-favicon-plus-reloaded = "^1.0.1"
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
-- 
GitLab