From 2e58ce9b020003a7e1e5f0a6494aafd6600b2514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Fri, 4 Jan 2019 20:56:08 +0100 Subject: [PATCH] pwa: add possibility to set application icon (#29457) --- .../0004_pwasettings_application_icon.py | 20 +++++++++++++ combo/apps/pwa/models.py | 9 ++++++ combo/apps/pwa/templates/combo/manifest.json | 14 +++++++++- .../pwa/templates/combo/pwa/manager_home.html | 8 ++++-- .../apps/pwa/templates/combo/pwa/offline.html | 9 ++++-- .../pwa/templates/combo/service-worker.js | 8 ++++-- combo/apps/pwa/views.py | 10 +++---- combo/context_processors.py | 4 +++ tests/test_pwa.py | 28 +++++++++++++++++++ 9 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 combo/apps/pwa/migrations/0004_pwasettings_application_icon.py diff --git a/combo/apps/pwa/migrations/0004_pwasettings_application_icon.py b/combo/apps/pwa/migrations/0004_pwasettings_application_icon.py new file mode 100644 index 00000000..453d4759 --- /dev/null +++ b/combo/apps/pwa/migrations/0004_pwasettings_application_icon.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2019-01-04 19:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pwa', '0003_pwanavigationentry'), + ] + + operations = [ + migrations.AddField( + model_name='pwasettings', + name='application_icon', + field=models.FileField(blank=True, help_text='Should be a square of at least 512\xd7512 pixels.', null=True, upload_to=b'pwa', verbose_name='Application Icon'), + ), + ] diff --git a/combo/apps/pwa/models.py b/combo/apps/pwa/models.py index 8f53df5f..47070827 100644 --- a/combo/apps/pwa/models.py +++ b/combo/apps/pwa/models.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -* +# # combo - content management system # Copyright (C) 2015-2018 Entr'ouvert # @@ -33,6 +35,13 @@ from combo import utils class PwaSettings(models.Model): + APPLICATION_ICON_SIZES = ['%sx%s' % (x, x) for x in (48, 96, 192, 256, 512)] + application_icon = models.FileField( + verbose_name=_('Application Icon'), + help_text=_(u'Should be a square of at least 512×512 pixels.'), + upload_to='pwa', + blank=True, + null=True) offline_text = RichTextField( verbose_name=_('Offline Information Text'), default=_('You are currently offline.'), diff --git a/combo/apps/pwa/templates/combo/manifest.json b/combo/apps/pwa/templates/combo/manifest.json index 1fd85fe8..bbb71d3d 100644 --- a/combo/apps/pwa/templates/combo/manifest.json +++ b/combo/apps/pwa/templates/combo/manifest.json @@ -1,4 +1,4 @@ -{% load static %}{ +{% load static thumbnail %}{ "name": "{% firstof global_title "Compte Citoyen" %}", "short_name": "{% firstof global_title "Compte Citoyen" %}", "start_url": "{% firstof pwa_start_url "/" %}", @@ -8,6 +8,17 @@ {% endif %} {% block icons %} "icons": [ + {% if pwa_settings.application_icon %} + {% for icon_size in pwa_settings.APPLICATION_ICON_SIZES %} + { + "sizes": "{{ icon_size }}", + {% thumbnail pwa_settings.application_icon icon_size crop='center' format='PNG' as im %} + "src": "{{ site_base }}{{ im.url }}", + {% endthumbnail %} + "type": "image/png" + }{% if not forloop.last %},{% endif %} + {% endfor %} + {% else %} {% for icon_size in icon_sizes %} { "sizes": "{{ icon_size }}x{{ icon_size }}", @@ -15,6 +26,7 @@ "type": "image/png" }{% if not forloop.last %},{% endif %} {% endfor %} + {% endif %} ], {% endblock %} {% block extra %} diff --git a/combo/apps/pwa/templates/combo/pwa/manager_home.html b/combo/apps/pwa/templates/combo/pwa/manager_home.html index 1d709b41..d9d2d1cb 100644 --- a/combo/apps/pwa/templates/combo/pwa/manager_home.html +++ b/combo/apps/pwa/templates/combo/pwa/manager_home.html @@ -1,5 +1,5 @@ {% extends "combo/pwa/manager_base.html" %} -{% load i18n static %} +{% load i18n thumbnail %} {% block content %}
@@ -9,7 +9,11 @@
- + {% if pwa_settings.application_icon %} + {% thumbnail pwa_settings.application_icon '512x512' crop='center' format='PNG' as im %} + + {% endthumbnail %} + {% endif %}
{% firstof global_title "Compte Citoyen" %}
diff --git a/combo/apps/pwa/templates/combo/pwa/offline.html b/combo/apps/pwa/templates/combo/pwa/offline.html index 99f8755c..415104d3 100644 --- a/combo/apps/pwa/templates/combo/pwa/offline.html +++ b/combo/apps/pwa/templates/combo/pwa/offline.html @@ -1,4 +1,4 @@ -{% load i18n static %} +{% load i18n thumbnail %} @@ -42,7 +42,12 @@ p.retry a {
- + {% if pwa_settings.application_icon %} + {% thumbnail pwa_settings.application_icon '512x512' crop='center' format='PNG' as im %} + + {% endthumbnail %} + {% endif %} + {{ pwa_settings.offline_text|safe }} {% if pwa_settings.offline_retry_button %} diff --git a/combo/apps/pwa/templates/combo/service-worker.js b/combo/apps/pwa/templates/combo/service-worker.js index 56c46ba1..92c6e317 100644 --- a/combo/apps/pwa/templates/combo/service-worker.js +++ b/combo/apps/pwa/templates/combo/service-worker.js @@ -1,4 +1,4 @@ -{% load combo gadjo static %} +{% load combo gadjo static thumbnail %} /* global self, caches, fetch, URL, Response */ 'use strict'; @@ -26,7 +26,11 @@ var config = { version: 'v{{ version }}', staticCacheItems: [ '/__pwa__/offline/', - '{% static "" %}{{ css_variant }}/{{ icon_prefix }}{{icon_sizes|last}}px.png' + {% if pwa_settings.application_icon %} + {% thumbnail pwa_settings.application_icon '512x512' crop='center' format='PNG' as im %} + '{{ im.url }}' + {% endthumbnail %} + {% endif %} ], cachePathPattern: /^\/static\/.*/, handleFetchPathPattern: /.*/, diff --git a/combo/apps/pwa/views.py b/combo/apps/pwa/views.py index 6785d4b8..b4c82e7b 100644 --- a/combo/apps/pwa/views.py +++ b/combo/apps/pwa/views.py @@ -32,7 +32,10 @@ def manifest_json(request, *args, **kwargs): template = get_template('combo/manifest.json') except TemplateDoesNotExist: raise Http404() - return HttpResponse(template.render({}, request), content_type='application/json') + context = { + 'site_base': request.build_absolute_uri('/')[:-1], + } + return HttpResponse(template.render(context, request), content_type='application/json') def js_response(request, template_name, **kwargs): @@ -81,9 +84,4 @@ def subscribe_push(request, *args, **kwargs): class OfflinePage(TemplateView): template_name = 'combo/pwa/offline.html' - def get_context_data(self, **kwargs): - context = super(OfflinePage, self).get_context_data(**kwargs) - context['pwa_settings'] = PwaSettings.singleton() - return context - offline_page = OfflinePage.as_view() diff --git a/combo/context_processors.py b/combo/context_processors.py index cde82244..66ba5ab9 100644 --- a/combo/context_processors.py +++ b/combo/context_processors.py @@ -17,8 +17,12 @@ from django.conf import settings +from combo.utils.cache import cache_during_request +from combo.apps.pwa.models import PwaSettings + def template_vars(request): context_extras = {} context_extras['debug'] = settings.DEBUG + context_extras['pwa_settings'] = cache_during_request(PwaSettings.singleton) context_extras.update(settings.TEMPLATE_VARS) return context_extras diff --git a/tests/test_pwa.py b/tests/test_pwa.py index 59e6d22d..c3753773 100644 --- a/tests/test_pwa.py +++ b/tests/test_pwa.py @@ -1,7 +1,9 @@ +import base64 import os import mock import pytest +from webtest import Upload try: import pywebpush @@ -137,6 +139,14 @@ def test_pwa_manager(app, admin_user): resp = resp.form.submit() assert 'An URL cannot be specified' in resp.text + # add an icon + resp = app.get('/manage/pwa/') + resp.form['application_icon'] = Upload('test.png', + base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='), + 'image/png') + resp = resp.form.submit().follow() + assert PwaSettings.singleton().application_icon.name == 'pwa/test.png' + def test_pwa_offline_page(app): PwaSettings.objects.all().delete() @@ -175,3 +185,21 @@ def test_pwa_navigation_templatetag(app): nav = t.render(Context({'request': request, 'render_skeleton': True})) assert 'data-pwa-user-name="{% block placeholder-user-name %}' in nav + + +def test_pwa_application_icon(app, admin_user): + app = login(app) + with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}): + PwaSettings.objects.all().delete() + resp = app.get('/manage/pwa/') + resp.form['application_icon'] = Upload('test.png', + base64.decodestring(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='), + 'image/png') + resp = resp.form.submit().follow() + + resp = app.get('/manifest.json', status=200) + assert len(resp.json['icons']) == 5 + + # make sure largest icon is referenced in service worker + resp2 = app.get('/service-worker.js', status=200) + assert resp.json['icons'][-1]['src'].split('/')[-1] in resp.text