Skip to content
Snippets Groups Projects
Verified Commit ae1e456d authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Include PWA support (merge from SchoolApps)

- Add django-pwa to pyproject.toml
- Add configuration for django-pwa
- Include PWA configuration and favicon support in base.html
- Include serviceworker.js and adapt it for AlekSIS
- Remove old files
parent 3f4f8693
No related branches found
No related tags found
1 merge request!99Add PWA to AlekSIS
Pipeline #500 failed
...@@ -192,36 +192,6 @@ else: ...@@ -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 = { LOGGING = {
'version': 1, 'version': 1,
......
...@@ -72,7 +72,8 @@ INSTALLED_APPS = [ ...@@ -72,7 +72,8 @@ INSTALLED_APPS = [
"aleksis.core", "aleksis.core",
"impersonate", "impersonate",
"two_factor", "two_factor",
"material" "material",
"pwa"
] ]
INSTALLED_APPS += get_app_packages() INSTALLED_APPS += get_app_packages()
...@@ -84,7 +85,6 @@ STATICFILES_FINDERS = [ ...@@ -84,7 +85,6 @@ STATICFILES_FINDERS = [
"sass_processor.finders.CssFinder", "sass_processor.finders.CssFinder",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
# 'django.middleware.cache.UpdateCacheMiddleware', # 'django.middleware.cache.UpdateCacheMiddleware',
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
...@@ -125,15 +125,14 @@ TEMPLATES = [ ...@@ -125,15 +125,14 @@ TEMPLATES = [
] ]
THUMBNAIL_PROCESSORS = ( THUMBNAIL_PROCESSORS = (
"image_cropping.thumbnail_processors.crop_corners", "image_cropping.thumbnail_processors.crop_corners",
) + thumbnail_settings.THUMBNAIL_PROCESSORS ) + thumbnail_settings.THUMBNAIL_PROCESSORS
# Already included by base template / Bootstrap # Already included by base template / Bootstrap
IMAGE_CROPPING_JQUERY_URL = None IMAGE_CROPPING_JQUERY_URL = None
WSGI_APPLICATION = "aleksis.core.wsgi.application" WSGI_APPLICATION = "aleksis.core.wsgi.application"
# Database # Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases # https://docs.djangoproject.com/en/2.1/ref/settings/#databases
...@@ -161,10 +160,10 @@ if _settings.get("caching.memcached.enabled", True): ...@@ -161,10 +160,10 @@ if _settings.get("caching.memcached.enabled", True):
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",}, {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", },
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", },
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", },
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", },
] ]
# Authentication backends are dynamically populated # Authentication backends are dynamically populated
...@@ -230,7 +229,8 @@ STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static")) ...@@ -230,7 +229,8 @@ STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static"))
MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media")) 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")) NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules"))
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", "highlight.js", "jquery", "manup", "materialize-css", "moment", "popper.js",
"prop-types", "react", "react-dom", "material-design-icons-iconfont", "select2"]
JS_URL = _settings.get("js_assets.url", STATIC_URL) JS_URL = _settings.get("js_assets.url", STATIC_URL)
JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules") JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules")
...@@ -304,3 +304,49 @@ if _settings.get("2fa.twilio.sid", None): ...@@ -304,3 +304,49 @@ if _settings.get("2fa.twilio.sid", None):
TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid") TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid")
_settings.populate_obj(sys.modules[__name__]) _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/icons/android_192.png",
"sizes": "192x192"
},
{
"src": "/static/icons/android_512.png",
"sizes": "512x512"
}
]
PWA_APP_ICONS_APPLE = [
{
"src": "/static/icons/apple_76.png",
"sizes": "76x76"
},
{
"src": "/static/icons/apple_114.png",
"sizes": "114x114"
},
{
"src": "/static/icons/apple_152.png",
"sizes": "152x152"
},
{
"src": "/static/icons/apple_180.png",
"sizes": "180x180"
},
]
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(STATIC_ROOT, 'js', 'serviceworker.js')
# PWA_APP_LANG = 'de-DE'
aleksis/core/static/common/favicon.ico

627 B

aleksis/core/static/common/logo.png

26.5 KiB

//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();
}
// 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();
}
{# -*- engine:django -*- #} {# -*- engine:django -*- #}
{% load i18n menu_generator static sass_tags any_js %} {% load i18n menu_generator static sass_tags any_js pwa %}
<!DOCTYPE html> <!DOCTYPE html>
...@@ -14,7 +14,13 @@ ...@@ -14,7 +14,13 @@
<title>School Information System</title> <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 #} {# CSS #}
{% include_css "material-design-icons" %} {% include_css "material-design-icons" %}
......
{# -*- 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' %}" />
...@@ -68,10 +68,8 @@ ...@@ -68,10 +68,8 @@
<!-- Favicon --> <!-- Favicon -->
<link rel="shortcut icon" type="image/x-icon" href="{% static 'common/favicon.ico' %}"> <link rel="shortcut icon" type="image/x-icon" href="{% static 'common/favicon.ico' %}">
<!-- PWA -->
{% progressive_web_app_meta %}
<!---------> <!--------->
<!-- CSS --> <!-- CSS -->
<!---------> <!--------->
<link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet"> <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet">
......
...@@ -11,6 +11,7 @@ from two_factor.urls import urlpatterns as tf_urls ...@@ -11,6 +11,7 @@ from two_factor.urls import urlpatterns as tf_urls
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("", include("pwa.urls"), name="pwa"),
path('offline/', views.offline, name='offline'), path('offline/', views.offline, name='offline'),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("data_management/", views.data_management, name="data_management"), path("data_management/", views.data_management, name="data_management"),
......
...@@ -161,9 +161,6 @@ optional = false ...@@ -161,9 +161,6 @@ optional = false
python-versions = "*" python-versions = "*"
version = "5.0.6" version = "5.0.6"
[package.dependencies]
six = "*"
[[package]] [[package]]
category = "dev" category = "dev"
description = "Code coverage measurement for Python" description = "Code coverage measurement for Python"
...@@ -427,6 +424,17 @@ version = ">=7.0.2" ...@@ -427,6 +424,17 @@ version = ">=7.0.2"
phonenumbers = ["phonenumbers (>=7.0.2)"] phonenumbers = ["phonenumbers (>=7.0.2)"]
phonenumberslite = ["phonenumberslite (>=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"
optional = false
python-versions = "*"
version = "1.0.6"
[package.dependencies]
django = ">=1.8"
[[package]] [[package]]
category = "main" category = "main"
description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." description = "SASS processor to compile SCSS files into *.css, while rendering, or offline."
...@@ -1226,7 +1234,7 @@ category = "main" ...@@ -1226,7 +1234,7 @@ category = "main"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
name = "pyyaml" name = "pyyaml"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = "*"
version = "5.2" version = "5.2"
[[package]] [[package]]
...@@ -1650,7 +1658,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"] ...@@ -1650,7 +1658,7 @@ testing = ["pathlib2", "contextlib2", "unittest2"]
ldap = ["django-auth-ldap"] ldap = ["django-auth-ldap"]
[metadata] [metadata]
content-hash = "40917beab5394838573def3e2c8a6719f37040d826df74b3cdc9b52262ad0290" content-hash = "7c5cc3497e09ab1ef53c542aeba9787d5045524e6f1a9c299454f18aa4d592a6"
python-versions = "^3.7" python-versions = "^3.7"
[metadata.files] [metadata.files]
...@@ -1828,6 +1836,10 @@ django-phonenumber-field = [ ...@@ -1828,6 +1836,10 @@ django-phonenumber-field = [
{file = "django-phonenumber-field-3.0.1.tar.gz", hash = "sha256:794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"}, {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"}, {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-sass-processor = [ django-sass-processor = [
{file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"}, {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"},
] ]
...@@ -2402,7 +2414,6 @@ urllib3 = [ ...@@ -2402,7 +2414,6 @@ urllib3 = [
] ]
wcwidth = [ wcwidth = [
{file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"}, {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"},
{file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"},
] ]
yubiotp = [ yubiotp = [
{file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"}, {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"},
......
...@@ -50,6 +50,7 @@ django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumb ...@@ -50,6 +50,7 @@ django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumb
django-yarnpkg = "^6.0" django-yarnpkg = "^6.0"
django-dbsettings = "^1.0.0" django-dbsettings = "^1.0.0"
django-material = "^1.6.0" django-material = "^1.6.0"
django-pwa = "^1.0.6"
[tool.poetry.extras] [tool.poetry.extras]
ldap = ["django-auth-ldap"] ldap = ["django-auth-ldap"]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment