diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000000000000000000000000000000000..f813351a7160f9a16f64e1e26868a139ce60ae56 --- /dev/null +++ b/.mailmap @@ -0,0 +1,16 @@ +Dominik George <dominik.george@teckids.org> Dominik George <nik@naturalnet.de> +Hangzhi Yu <hangzhi@protonmail.com> Hangzhi <hangzhi@protonmail.com> +Hangzhi Yu <hangzhi@protonmail.com> Hnagzhi <37748390+Hnagzhi@users.noreply.github.com> +Jonathan Weth <git@jonathanweth.de> HanseGucker <hansegucker@gmail.com> +Jonathan Weth <git@jonathanweth.de> HanseGucker <joniweth@gmx.de> +Jonathan Weth <git@jonathanweth.de> Jonathan Weth <edugit@jonathanweth.de> +Jonathan Weth <git@jonathanweth.de> Jonathan Weth <joniweth@gmx.de> +Jonathan Weth <git@jonathanweth.de> Jonathan Weth <mail@jonathanweth.de> +Jonathan Weth <git@jonathanweth.de> Jonathan Weth <wethjo@katharineum.de> +Julian Leucker <leuckerj@gmail.com> Julian <leuckerj@gmail.com> +Silas Della Contrada <s.developer@4-dc.de> sdcieo0330 <silasdc0@gmail.com> +mirabilos <thorsten.glaser@teckids.org> mirabilos <mirabilos@evolvis.org> +mirabilos <thorsten.glaser@teckids.org> mirabilos <t.glaser@tarent.de> +root (Skolelinux) <root@tjener.intern> root <root@tjener.intern> +root <root@katharineum.de> GitHub <noreply@github.com> +root <root@katharineum.de> root <root@info.katharineum.de> diff --git a/aleksis/core/dashboard/views/tools.py b/aleksis/core/dashboard/views/tools.py index 0bcdee0d52aafc23e1e99b82d42687a9f55ee708..5c088dcf6744118713f15924f9d56069dee7d01a 100644 --- a/aleksis/core/dashboard/views/tools.py +++ b/aleksis/core/dashboard/views/tools.py @@ -3,9 +3,5 @@ from django.shortcuts import render from meta import OPEN_SOURCE_COMPONENTS -def offline(request): - return render(request, 'common/offline.html') - - def about(request): return render(request, "common/about.html", context={"components": OPEN_SOURCE_COMPONENTS}) diff --git a/aleksis/core/schoolapps/settings.py b/aleksis/core/schoolapps/settings.py index 36fd54aa760b3c7a73a72754b972a28b659b2241..5d59cc743dcd60426eee3f471799a3bbbe5370e7 100644 --- a/aleksis/core/schoolapps/settings.py +++ b/aleksis/core/schoolapps/settings.py @@ -192,36 +192,6 @@ else: } } -# PWA -PWA_APP_NAME = 'SchoolApps' -PWA_APP_DESCRIPTION = "Eine Sammlung an nützlichen Apps für den Schulalltag am Katharineum zu Lübeck" -PWA_APP_THEME_COLOR = '#da1f3d' -PWA_APP_BACKGROUND_COLOR = '#ffffff' -PWA_APP_DISPLAY = 'standalone' -PWA_APP_SCOPE = '/' -PWA_APP_ORIENTATION = 'any' -PWA_APP_START_URL = '/' -PWA_APP_ICONS = [ - { - "src": "/static/icons/android_192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/static/icons/android_512.png", - "sizes": "512x512", - "type": "image/png" - } -] -PWA_APP_SPLASH_SCREEN = [ - { - 'src': '/static/icons/android_512.png', - 'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)' - } -] -PWA_APP_DIR = 'ltr' -PWA_SERVICE_WORKER_PATH = os.path.join(BASE_DIR, 'static/common', 'serviceworker.js') -PWA_APP_LANG = 'de-DE' LOGGING = { 'version': 1, diff --git a/aleksis/core/schoolapps/urls.py b/aleksis/core/schoolapps/urls.py index ba42b84d22a00a0aefe57eb87e08ac9daf6fba37..b96d8de1368cb7f3af1cf79b6f79012367a60a59 100644 --- a/aleksis/core/schoolapps/urls.py +++ b/aleksis/core/schoolapps/urls.py @@ -77,7 +77,6 @@ urlpatterns = [ ####### path('faq/', include('faq.urls')), - path('', include('pwa.urls')), path('martor/', include('martor.urls')), diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index a8ef779f011f048223807a0c8b2ef8f43fbacffa..5ee6c8e493b0c819a1769b92cb4fb6250d96447c 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -73,7 +73,8 @@ INSTALLED_APPS = [ "aleksis.core", "impersonate", "two_factor", - "material" + "material", + "pwa", ] INSTALLED_APPS += get_app_packages() @@ -85,7 +86,6 @@ STATICFILES_FINDERS = [ "sass_processor.finders.CssFinder", ] - MIDDLEWARE = [ # 'django.middleware.cache.UpdateCacheMiddleware', "django.middleware.security.SecurityMiddleware", @@ -120,7 +120,7 @@ TEMPLATES = [ "django.contrib.messages.context_processors.messages", "maintenance_mode.context_processors.maintenance_mode", "settings_context_processor.context_processors.settings", - "constance.context_processors.config" + "constance.context_processors.config", ], }, }, @@ -135,7 +135,6 @@ IMAGE_CROPPING_JQUERY_URL = None WSGI_APPLICATION = "aleksis.core.wsgi.application" - # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases @@ -232,7 +231,13 @@ STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static")) MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media")) NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules")) -YARN_INSTALLED_APPS = ["datatables", "jquery", "materialize-css", "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") @@ -245,7 +250,9 @@ ANY_JS = { "DataTables": {"js_url": JS_URL + "/datatables/media/js/jquery.dataTables.min.js"}, "materialize": {"js_url": JS_URL + "/materialize-css/dist/js/materialize.min.js"}, "jQuery": {"js_url": JS_URL + "/jquery/dist/jquery.min.js"}, - "material-design-icons": {"css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css"}, + "material-design-icons": { + "css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css" + }, } SASS_PROCESSOR_AUTO_INCLUDE = False @@ -253,7 +260,10 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = { "get-colour": "aleksis.core.util.sass_helpers.get_colour", "get-config": "aleksis.core.util.sass_helpers.get_config", } -SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"), STATIC_ROOT] +SASS_PROCESSOR_INCLUDE_DIRS = [ + _settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"), + STATIC_ROOT, +] ADMINS = _settings.get("contact.admins", []) SERVER_EMAIL = _settings.get("contact.from", "root@localhost") @@ -272,7 +282,7 @@ if _settings.get("mail.server.host", None): TEMPLATE_VISIBLE_SETTINGS = ["ADMINS", "DEBUG"] -CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' +CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" CONSTANCE_ADDITIONAL_FIELDS = { "image_field": ["django.forms.ImageField", {}], "email_field": ["django.forms.EmailField", {}], @@ -326,3 +336,31 @@ if _settings.get("2fa.twilio.sid", None): TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid") _settings.populate_obj(sys.modules[__name__]) + +PWA_APP_NAME = "AlekSIS" # dbsettings +PWA_APP_DESCRIPTION = "AlekSIS – The free school information system" # dbsettings +PWA_APP_THEME_COLOR = _settings.get("pwa.color", "#da1f3d") # dbsettings +PWA_APP_BACKGROUND_COLOR = "#ffffff" +PWA_APP_DISPLAY = "standalone" +PWA_APP_SCOPE = "/" +PWA_APP_ORIENTATION = "any" +PWA_APP_START_URL = "/" +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"}, +] +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"}, +] +PWA_APP_SPLASH_SCREEN = [ + { + "src": STATIC_URL + "/icons/android_512.png", + "media": "(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)", + } +] +PWA_APP_DIR = "ltr" +PWA_SERVICE_WORKER_PATH = os.path.join(STATIC_ROOT, "js", "serviceworker.js") +# PWA_APP_LANG = 'de-DE' diff --git a/aleksis/core/static/common/favicon.ico b/aleksis/core/static/common/favicon.ico deleted file mode 100644 index 1405a12165083ca70bcdfffeacc527f5986a3785..0000000000000000000000000000000000000000 Binary files a/aleksis/core/static/common/favicon.ico and /dev/null differ diff --git a/aleksis/core/static/common/logo.png b/aleksis/core/static/common/logo.png deleted file mode 100644 index 005e0b6c64a3475a0ccaa875caaf7673c14b8fd2..0000000000000000000000000000000000000000 Binary files a/aleksis/core/static/common/logo.png and /dev/null differ diff --git a/aleksis/core/static/common/serviceworker.js b/aleksis/core/static/common/serviceworker.js deleted file mode 100644 index 76a52613b4a8a08e45d06b1fdcc54a74e47f5913..0000000000000000000000000000000000000000 --- a/aleksis/core/static/common/serviceworker.js +++ /dev/null @@ -1,115 +0,0 @@ -//This is the SchoolApps service worker - -const CACHE = "schoolapps-cache"; - -const precacheFiles = [ - '', - '/faq/', -]; - -const offlineFallbackPage = '/offline'; - -const avoidCachingPaths = [ - '/admin', - '/settings', - '/support', - '/tools', - '/faq/ask', - '/aub/apply_for', - '/aub/check1', - '/aub/check2', - '/aktuell.pdf', - '/accounts/login', - '/timetable/aktuell.pdf', - '/api', -]; - -function pathComparer(requestUrl, pathRegEx) { - return requestUrl.match(new RegExp(pathRegEx)); -} - -function comparePaths(requestUrl, pathsArray) { - if (requestUrl) { - for (let index = 0; index < pathsArray.length; index++) { - const pathRegEx = pathsArray[index]; - if (pathComparer(requestUrl, pathRegEx)) { - return true; - } - } - } - - return false; -} - -self.addEventListener("install", function (event) { - console.log("[SchoolApps PWA] Install Event processing."); - - console.log("[SchoolApps PWA] Skipping waiting on install."); - self.skipWaiting(); - - event.waitUntil( - caches.open(CACHE).then(function (cache) { - console.log("[SchoolApps PWA] Caching pages during install."); - - return cache.addAll(precacheFiles).then(function () { - return cache.add(offlineFallbackPage); - }); - }) - ); -}); - -// Allow sw to control of current page -self.addEventListener("activate", function (event) { - console.log("[SchoolApps PWA] Claiming clients for current page."); - event.waitUntil(self.clients.claim()); -}); - -// If any fetch fails, it will look for the request in the cache and serve it from there first -self.addEventListener("fetch", function (event) { - if (event.request.method !== "GET") return; - networkFirstFetch(event); -}); - -function networkFirstFetch(event) { - event.respondWith( - fetch(event.request) - .then(function (response) { - // If request was successful, add or update it in the cache - console.log("[SchoolApps PWA] Network request successful."); - event.waitUntil(updateCache(event.request, response.clone())); - return response; - }) - .catch(function (error) { - console.log("[SchoolApps PWA] Network request failed. Serving content from cache: " + error); - return fromCache(event); - }) - ); -} - -function fromCache(event) { - // Check to see if you have it in the cache - // Return response - // If not in the cache, then return offline fallback page - return caches.open(CACHE).then(function (cache) { - return cache.match(event.request) - .then(function (matching) { - if (!matching || matching.status === 404) { - console.log("[SchoolApps PWA] Cache request failed. Serving offline fallback page."); - // Use the precached offline page as fallback - return caches.match(offlineFallbackPage) - } - - return matching; - }); - }); -} - -function updateCache(request, response) { - if (!comparePaths(request.url, avoidCachingPaths)) { - return caches.open(CACHE).then(function (cache) { - return cache.put(request, response); - }); - } - - return Promise.resolve(); -} diff --git a/aleksis/core/static/js/serviceworker.js b/aleksis/core/static/js/serviceworker.js new file mode 100644 index 0000000000000000000000000000000000000000..93d9797c78db9756cba6f1f6caa46fc8c14ae57b --- /dev/null +++ b/aleksis/core/static/js/serviceworker.js @@ -0,0 +1,105 @@ +// This is the AlekSIS service worker + +const CACHE = "aleksis-cache"; + +const precacheFiles = [ + '', +]; + +const offlineFallbackPage = '/offline'; + +const avoidCachingPaths = [ + '/admin', + '/settings', + '/accounts/login' +]; // TODO: More paths are needed + +function pathComparer(requestUrl, pathRegEx) { + return requestUrl.match(new RegExp(pathRegEx)); +} + +function comparePaths(requestUrl, pathsArray) { + if (requestUrl) { + for (let index = 0; index < pathsArray.length; index++) { + const pathRegEx = pathsArray[index]; + if (pathComparer(requestUrl, pathRegEx)) { + return true; + } + } + } + + return false; +} + +self.addEventListener("install", function (event) { + console.log("[AlekSIS PWA] Install Event processing."); + + console.log("[AlekSIS PWA] Skipping waiting on install."); + self.skipWaiting(); + + event.waitUntil( + caches.open(CACHE).then(function (cache) { + console.log("[AlekSIS PWA] Caching pages during install."); + + return cache.addAll(precacheFiles).then(function () { + return cache.add(offlineFallbackPage); + }); + }) + ); +}); + +// Allow sw to control of current page +self.addEventListener("activate", function (event) { + console.log("[AlekSIS PWA] Claiming clients for current page."); + event.waitUntil(self.clients.claim()); +}); + +// If any fetch fails, it will look for the request in the cache and serve it from there first +self.addEventListener("fetch", function (event) { + if (event.request.method !== "GET") return; + networkFirstFetch(event); +}); + +function networkFirstFetch(event) { + event.respondWith( + fetch(event.request) + .then(function (response) { + // If request was successful, add or update it in the cache + console.log("[AlekSIS PWA] Network request successful."); + event.waitUntil(updateCache(event.request, response.clone())); + return response; + }) + .catch(function (error) { + console.log("[AlekSIS PWA] Network request failed. Serving content from cache: " + error); + return fromCache(event); + }) + ); +} + +function fromCache(event) { + // Check to see if you have it in the cache + // Return response + // If not in the cache, then return offline fallback page + return caches.open(CACHE).then(function (cache) { + return cache.match(event.request) + .then(function (matching) { + if (!matching || matching.status === 404) { + console.log("[AlekSIS PWA] Cache request failed. Serving offline fallback page."); + // Use the precached offline page as fallback + return caches.match(offlineFallbackPage) + } + + return matching; + }); + }); +} + +function updateCache(request, response) { + if (!comparePaths(request.url, avoidCachingPaths)) { + return caches.open(CACHE).then(function (cache) { + return cache.put(request, response); + }); + } + + return Promise.resolve(); +} diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index a612ec66af3e782faa6ec441b0303425995fc474..e98f71587ed93f987ed710c0dd7721f86f21a407 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -1,7 +1,7 @@ {# -*- engine:django -*- #} -{% load i18n menu_generator static sass_tags any_js %} +{% load i18n menu_generator static sass_tags any_js pwa %} <!DOCTYPE html> @@ -14,7 +14,13 @@ <title>School Information System</title> - {% include 'core/icons.html' %} + {# 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"> + + <!-- PWA --> + {% progressive_web_app_meta %} {# CSS #} {% include_css "material-design-icons" %} diff --git a/aleksis/core/templates/core/icons.html b/aleksis/core/templates/core/icons.html deleted file mode 100644 index 53e98aadd7f53a25431dfc1fa6d72aa9b53617a1..0000000000000000000000000000000000000000 --- a/aleksis/core/templates/core/icons.html +++ /dev/null @@ -1,7 +0,0 @@ -{# -*- engine:django -*- #} - -{% load static %} - -<link rel="shortcut icon" type="image/png" href="{% static 'img/aleksis-logo.png' %}" /> -<link rel="shortcut icon" sizes="196x196" href="{% static 'img/aleksis-logo.png' %}" /> -<link rel="apple-touch-icon" href="{% static 'img/aleksis-logo.png' %}" /> diff --git a/aleksis/core/templates/partials/header.html b/aleksis/core/templates/partials/header.html index ceb4065c957413766d0716a3bffe7c4a4953d5d9..c3edc1caa05eb2e6863ffef1ea3e7672059b05bb 100644 --- a/aleksis/core/templates/partials/header.html +++ b/aleksis/core/templates/partials/header.html @@ -68,10 +68,8 @@ <!-- Favicon --> <link rel="shortcut icon" type="image/x-icon" href="{% static 'common/favicon.ico' %}"> - <!-- PWA --> - {% progressive_web_app_meta %} - <!---------> + <!---------> <!-- CSS --> <!---------> <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet"> diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 17e71cc0c4d928e52bd0ecdd3f24c801b1574c3d..0ac57aabe965aa6017d455a3468eddf43f0abe80 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -11,6 +11,8 @@ from two_factor.urls import urlpatterns as tf_urls from . import views urlpatterns = [ + path("", include("pwa.urls"), name="pwa"), + path("offline/", views.offline, name="offline"), path("admin/", admin.site.urls), path("data_management/", views.data_management, name="data_management"), path("status/", views.system_status, name="system_status"), diff --git a/aleksis/core/views.py b/aleksis/core/views.py index c435da07c021dc8b8c557a70b3e4be46eb26f66d..709ebe26d917c4d5fad9ffe2afc0ccdbab4bb2db 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -25,6 +25,10 @@ def index(request: HttpRequest) -> HttpResponse: return render(request, "core/index.html", context) +def offline(request): + return render(request, "common/offline.html") + + @login_required def persons(request: HttpRequest) -> HttpResponse: context = {} diff --git a/dev.sh b/dev.sh index 582cd9bb636b5cabc7fe904b40dd3186ab7ec64c..5d9331eba103a60325e691975064f35355444d05 100755 --- a/dev.sh +++ b/dev.sh @@ -46,7 +46,7 @@ case "$1" in done ;; "gource") - for d in aleksis/core apps/official/*/aleksis/apps/*; do + for d in . apps/official/*; do gource --output-custom-log - "$d" done | sort -n | gource --log-format custom --background-image aleksis/core/static/img/aleksis-logo.png - ;; diff --git a/pyproject.toml b/pyproject.toml index 72552092a7ad82aca51d4cc3e0fbb07cde049595..15f4716bb42aaaf793586ec572d5998c50867217 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ requests = "^2.22" django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] } django-yarnpkg = "^6.0" django-material = "^1.6.0" +django-pwa = "^1.0.6" django-constance = {git = "https://github.com/jazzband/django-constance", rev = "590fa02eb30e377da0eda5cc3a84254b839176a7", extras = ["database"]} [tool.poetry.extras] diff --git a/requirements.txt b/requirements.txt index 8b767d1c91300b74fb4bce041688565787ae8949..08d6e1aa88a09416668bf8e0238fe0ef44cff4bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,5 @@ django-filter django_react_templatetags PyPDF2 martor -django-pwa django_widget_tweaks ics