0001-pwa-add-settings-page-with-offline-parameters-25496.patch
combo/apps/pwa/__init__.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import django.apps |
18 |
from django.core.urlresolvers import reverse |
|
19 |
from django.utils.translation import ugettext_lazy as _ |
|
20 | ||
18 | 21 | |
19 | 22 |
class AppConfig(django.apps.AppConfig): |
20 | 23 |
name = 'combo.apps.pwa' |
... | ... | |
26 | 29 |
from . import urls |
27 | 30 |
return urls.urlpatterns |
28 | 31 | |
32 |
def get_extra_manager_actions(self): |
|
33 |
from django.conf import settings |
|
34 |
if settings.TEMPLATE_VARS.get('pwa_display') in ('standalone', 'fullscreen'): |
|
35 |
return [{'href': reverse('pwa-manager-homepage'), |
|
36 |
'text': _('Mobile Application (PWA)')}] |
|
37 |
return [] |
|
38 | ||
29 | 39 |
default_app_config = 'combo.apps.pwa.AppConfig' |
combo/apps/pwa/manager_views.py | ||
---|---|---|
1 |
# combo - content management system |
|
2 |
# Copyright (C) 2018 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.core.urlresolvers import reverse_lazy |
|
18 |
from django.views.generic import UpdateView |
|
19 | ||
20 |
from .models import PwaSettings |
|
21 | ||
22 | ||
23 |
class ManagerHomeView(UpdateView): |
|
24 |
template_name = 'combo/pwa/manager_home.html' |
|
25 |
model = PwaSettings |
|
26 |
fields = '__all__' |
|
27 |
success_url = reverse_lazy('pwa-manager-homepage') |
|
28 | ||
29 |
def get_object(self): |
|
30 |
return PwaSettings.singleton() |
combo/apps/pwa/migrations/0002_pwasettings.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.12 on 2018-12-27 08:44 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import combo.data.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('pwa', '0001_initial'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.CreateModel( |
|
17 |
name='PwaSettings', |
|
18 |
fields=[ |
|
19 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
20 |
('offline_text', combo.data.fields.RichTextField(default='You are currently offline.', verbose_name='Offline Information Text')), |
|
21 |
('offline_retry_button', models.BooleanField(default=True, verbose_name='Include Retry Button')), |
|
22 |
('last_update_timestamp', models.DateTimeField(auto_now=True)), |
|
23 |
], |
|
24 |
), |
|
25 |
] |
combo/apps/pwa/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import json |
|
18 | ||
17 | 19 |
from django.conf import settings |
20 |
from django.core import serializers |
|
18 | 21 |
from django.db import models |
22 |
from django.utils.translation import ugettext_lazy as _ |
|
19 | 23 | |
20 | 24 |
from jsonfield import JSONField |
25 |
from combo.data.fields import RichTextField |
|
26 | ||
27 | ||
28 |
class PwaSettings(models.Model): |
|
29 |
offline_text = RichTextField( |
|
30 |
verbose_name=_('Offline Information Text'), |
|
31 |
default=_('You are currently offline.'), |
|
32 |
config_name='small') |
|
33 |
offline_retry_button = models.BooleanField(_('Include Retry Button'), default=True) |
|
34 |
last_update_timestamp = models.DateTimeField(auto_now=True) |
|
35 | ||
36 |
@classmethod |
|
37 |
def singleton(cls): |
|
38 |
return cls.objects.all().first() or cls() |
|
39 | ||
40 |
@classmethod |
|
41 |
def export_for_json(cls): |
|
42 |
obj = cls.singleton() |
|
43 |
if not obj.id: |
|
44 |
return {} |
|
45 |
serialized_settings = json.loads(serializers.serialize('json', [obj])) |
|
46 |
return serialized_settings[0].get('fields') |
|
47 | ||
48 |
@classmethod |
|
49 |
def load_serialized_settings(cls, json_settings): |
|
50 |
if not json_settings: |
|
51 |
return |
|
52 | ||
53 |
obj = cls.singleton() |
|
54 |
for attr in json_settings: |
|
55 |
setattr(obj, attr, json_settings[attr]) |
|
56 |
obj.save() |
|
21 | 57 | |
22 | 58 | |
23 | 59 |
class PushSubscription(models.Model): |
combo/apps/pwa/static/css/combo.manager.pwa.scss | ||
---|---|---|
1 |
.manager-mobile-home-layout { |
|
2 |
display: flex; |
|
3 |
div.sections { |
|
4 |
flex: 1; |
|
5 |
} |
|
6 |
} |
|
7 | ||
8 |
div#mobile-case { |
|
9 |
background: url(../img/mobile-case.svg) top left no-repeat; |
|
10 |
width: 400px; |
|
11 |
height: 720px; |
|
12 |
position: relative; |
|
13 |
overflow: hidden; |
|
14 |
div.screen { |
|
15 |
position: absolute; |
|
16 |
overflow: hidden; |
|
17 |
left: 12px; |
|
18 |
top: 52px; |
|
19 |
bottom: 67px; |
|
20 |
right: 28px; |
|
21 |
div.mobile-top-bar { |
|
22 |
position: absolute; |
|
23 |
top: 0; |
|
24 |
left: 0; |
|
25 |
background: rgba(0, 0, 0, 0.7); |
|
26 |
width: 100%; |
|
27 |
text-align: right; |
|
28 |
color: white; |
|
29 |
box-sizing: border-box; |
|
30 |
padding-right: 5px; |
|
31 |
height: 20px; |
|
32 |
} |
|
33 |
div.mobile-app-content { |
|
34 |
position: absolute; |
|
35 |
top: 20px; |
|
36 |
left: 0; |
|
37 |
width: 100%; |
|
38 |
bottom: 0; |
|
39 |
div.splash, |
|
40 |
iframe { |
|
41 |
position: absolute; |
|
42 |
top: 0; |
|
43 |
left: 0; |
|
44 |
width: 100%; |
|
45 |
height: 100%; |
|
46 |
z-index: 0; |
|
47 |
opacity: 0; |
|
48 |
transition: all ease-out 0.4s; |
|
49 |
} |
|
50 |
div.splash { |
|
51 |
z-index: 100; |
|
52 |
opacity: 1; |
|
53 |
transform: scale(1); |
|
54 |
} |
|
55 |
&.splash-off { |
|
56 |
div.splash { |
|
57 |
pointer-events: none; |
|
58 |
opacity: 0; |
|
59 |
transform: scale(10); |
|
60 |
} |
|
61 |
iframe { |
|
62 |
opacity: 1; |
|
63 |
} |
|
64 |
} |
|
65 |
} |
|
66 |
div.appicon { |
|
67 |
position: absolute; |
|
68 |
top: 40%; |
|
69 |
text-align: center; |
|
70 |
img { |
|
71 |
width: 50%; |
|
72 |
} |
|
73 |
} |
|
74 |
div.applabel { |
|
75 |
position: absolute; |
|
76 |
bottom: 50px; |
|
77 |
left: 0; |
|
78 |
width: 100%; |
|
79 |
text-align: center; |
|
80 |
font-size: 30px; |
|
81 |
color: white; |
|
82 |
} |
|
83 |
} |
|
84 |
} |
combo/apps/pwa/static/img/mobile-case.svg | ||
---|---|---|
1 |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
|
2 |
<!-- Created with Inkscape (http://www.inkscape.org/) --> |
|
3 | ||
4 |
<svg |
|
5 |
xmlns:dc="http://purl.org/dc/elements/1.1/" |
|
6 |
xmlns:cc="http://creativecommons.org/ns#" |
|
7 |
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
|
8 |
xmlns:svg="http://www.w3.org/2000/svg" |
|
9 |
xmlns="http://www.w3.org/2000/svg" |
|
10 |
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
|
11 |
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
|
12 |
version="1.1" |
|
13 |
id="svg2" |
|
14 |
xml:space="preserve" |
|
15 |
width="386.93579" |
|
16 |
height="707.42499" |
|
17 |
viewBox="0 0 386.93579 707.42499" |
|
18 |
sodipodi:docname="mobile-case.svg" |
|
19 |
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata |
|
20 |
id="metadata8"><rdf:RDF><cc:Work |
|
21 |
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type |
|
22 |
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs |
|
23 |
id="defs6"><clipPath |
|
24 |
clipPathUnits="userSpaceOnUse" |
|
25 |
id="clipPath18"><path |
|
26 |
d="M 0,410 H 1028 V 0 H 0 Z" |
|
27 |
id="path16" |
|
28 |
inkscape:connector-curvature="0" /></clipPath></defs><sodipodi:namedview |
|
29 |
pagecolor="#ffffff" |
|
30 |
bordercolor="#666666" |
|
31 |
borderopacity="1" |
|
32 |
objecttolerance="10" |
|
33 |
gridtolerance="10" |
|
34 |
guidetolerance="10" |
|
35 |
inkscape:pageopacity="0" |
|
36 |
inkscape:pageshadow="2" |
|
37 |
inkscape:window-width="1920" |
|
38 |
inkscape:window-height="1043" |
|
39 |
id="namedview4" |
|
40 |
showgrid="false" |
|
41 |
inkscape:zoom="0.5" |
|
42 |
inkscape:cx="-114.43322" |
|
43 |
inkscape:cy="238.84637" |
|
44 |
inkscape:window-x="0" |
|
45 |
inkscape:window-y="0" |
|
46 |
inkscape:window-maximized="1" |
|
47 |
inkscape:current-layer="g10" |
|
48 |
inkscape:measure-start="27,428" |
|
49 |
inkscape:measure-end="387,428" |
|
50 |
fit-margin-top="0" |
|
51 |
fit-margin-left="0" |
|
52 |
fit-margin-right="0" |
|
53 |
fit-margin-bottom="0" /><g |
|
54 |
id="g10" |
|
55 |
inkscape:groupmode="layer" |
|
56 |
inkscape:label="x" |
|
57 |
transform="matrix(1.3333333,0,0,-1.3333333,-3.6475299,539.99784)"><g |
|
58 |
id="g20" |
|
59 |
transform="matrix(1.8601685,0,0,1.8601685,309.66345,-151.14733)" |
|
60 |
style="stroke-width:0.5"><path |
|
61 |
d="m -10.548622,33.67008 c 0,-16.568 -13.431,-19.920268 -29.999,-19.920268 H -134.999 c -16.569,0 -30.001,3.352268 -30.001,19.920268 V 280.665 c 0,9.72565 13.432,18.31099 30.001,18.31099 h 94.451378 c 16.568,0 29.999,-9.44063 29.999,-18.31099 z" |
|
62 |
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
63 |
id="path22" |
|
64 |
inkscape:connector-curvature="0" |
|
65 |
sodipodi:nodetypes="sssssssss" /></g><g |
|
66 |
id="g24" |
|
67 |
transform="matrix(1.8601685,0,0,1.8601685,300.67326,-142.46964)" |
|
68 |
style="stroke-width:0.5"><path |
|
69 |
d="m -10.120975,32.690552 c 0,-16.568 -13.432,-20.726646 -30,-20.726646 H -125.333 c -16.569,0 -30,4.158646 -30,20.726646 V 271.334 c 0,16.569 13.431,20.71282 30,20.71282 h 85.212025 c 16.568,0 30,-4.14382 30,-20.71282 z" |
|
70 |
style="fill:#232323;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
71 |
id="path26" |
|
72 |
inkscape:connector-curvature="0" |
|
73 |
sodipodi:nodetypes="sssssssss" /></g><g |
|
74 |
id="g30" |
|
75 |
transform="matrix(1.8601685,0,0,1.8601685,189.06408,381.94033)" |
|
76 |
style="stroke-width:0.5"><path |
|
77 |
d="m 0,0 c 0,-1 -1,-2 -2,-2 h -30 c -1,0 -2,1 -2,2 0,1 1,2 2,2 H -2 C -1,2 0,1 0,0" |
|
78 |
style="fill:#0e0e0e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
79 |
id="path32" |
|
80 |
inkscape:connector-curvature="0" /></g><g |
|
81 |
id="g38" |
|
82 |
transform="matrix(1.8601685,0,0,1.8601685,111.24393,381.94125)" |
|
83 |
style="stroke-width:0.5"><path |
|
84 |
d="m 0,0 c 0,-2 -1,-4 -4,-4 -2,0 -4,1 -4,4 0,2 1,4 4,4 2,0 4,-1 4,-4" |
|
85 |
style="fill:#0e0e0e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
86 |
id="path40" |
|
87 |
inkscape:connector-curvature="0" /></g><g |
|
88 |
id="g42" |
|
89 |
transform="matrix(1.8601685,0,0,1.8601685,108.45368,381.94125)" |
|
90 |
style="stroke-width:0.5"><path |
|
91 |
d="m 0,0 c 0,-1 -1,-2 -2,-2 -1,0 -3,1 -3,2 0,1 2,2 3,2 1,0 2,-1 2,-2" |
|
92 |
style="fill:#2c2c2c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
93 |
id="path44" |
|
94 |
inkscape:connector-curvature="0" /></g><g |
|
95 |
id="g46" |
|
96 |
transform="matrix(1.8601685,0,0,1.8601685,106.43911,381.94125)" |
|
97 |
style="stroke-width:0.5"><path |
|
98 |
d="m 0,0 c 0,0 0,-1 -1,-1 0,0 -1,0 -1,1 0,0 0,1 1,1 1,0 1,-1 1,-1" |
|
99 |
style="fill:#373737;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5" |
|
100 |
id="path48" |
|
101 |
inkscape:connector-curvature="0" /></g><path |
|
102 |
d="m 292.9375,309.4375 h -3 v 41 h 3 z" |
|
103 |
style="fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1" |
|
104 |
id="path50" |
|
105 |
inkscape:connector-curvature="0" /><rect |
|
106 |
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.8;fill:#00ffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28346458;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" |
|
107 |
id="rect831" |
|
108 |
width="270" |
|
109 |
height="450" |
|
110 |
x="11.735635" |
|
111 |
y="-365.1778" |
|
112 |
transform="scale(1,-1)" /></g></svg> |
combo/apps/pwa/templates/combo/pwa/manager_base.html | ||
---|---|---|
1 |
{% extends "combo/manager_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block css %} |
|
5 |
{{ block.super }} |
|
6 |
<link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/combo.manager.pwa.css"/> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans 'Mobile Application' %}</h2> |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block breadcrumb %} |
|
14 |
{{ block.super }} |
|
15 |
<a href="{% url 'pwa-manager-homepage' %}">{% trans 'Mobile Application' %}</a> |
|
16 |
{% endblock %} |
combo/apps/pwa/templates/combo/pwa/manager_home.html | ||
---|---|---|
1 |
{% extends "combo/pwa/manager_base.html" %} |
|
2 |
{% load i18n static %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
<div class="manager-mobile-home-layout"> |
|
6 |
<div id="mobile-case"> |
|
7 |
<div class="screen" style="background: {{ theme_color }};"> |
|
8 |
<div class="mobile-top-bar"><span class="clock">--:--</span></div> |
|
9 |
<div class="mobile-app-content"> |
|
10 |
<div class="splash"> |
|
11 |
<div class="appicon"> |
|
12 |
<img src="{% static "" %}{{ css_variant }}/{{ icon_prefix }}{{icon_sizes|last}}px.png" alt=""> |
|
13 |
</div> |
|
14 |
<div class="applabel">{% firstof global_title "Compte Citoyen" %}</div> |
|
15 |
</div> |
|
16 |
<iframe scrolling="no"></iframe> |
|
17 |
</div> |
|
18 |
</div> |
|
19 |
</div> |
|
20 | ||
21 |
<div class="sections"> |
|
22 | ||
23 |
<div class="section settings"> |
|
24 |
<h3>{% trans "Settings" %}</h3> |
|
25 |
<div> |
|
26 |
<form method="post" enctype="multipart/form-data"> |
|
27 |
{% csrf_token %} |
|
28 |
{{ form.as_p }} |
|
29 |
<div class="buttons"> |
|
30 |
<button class="submit-button">{% trans "Save" %}</button> |
|
31 |
</div> |
|
32 |
</form> |
|
33 |
</div> |
|
34 |
</div> |
|
35 | ||
36 |
</div> {# .sections #} |
|
37 |
</div> {# .manager-mobile-home-layout #} |
|
38 | ||
39 |
<script> |
|
40 |
setInterval(function() { |
|
41 |
var $clock = $('#mobile-case .clock'); |
|
42 |
var date = new Date(); |
|
43 |
$clock.text(('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2)); |
|
44 |
}, 500); |
|
45 | ||
46 |
$(function() { |
|
47 |
$('.mobile-app-content .splash').on('click', function() { |
|
48 |
$('.mobile-app-content iframe').attr('src', '/'); |
|
49 |
$('.mobile-app-content').addClass('splash-off'); |
|
50 |
}); |
|
51 |
}); |
|
52 |
</script> |
|
53 | ||
54 |
{% endblock %} |
combo/apps/pwa/templates/combo/pwa/offline.html | ||
---|---|---|
1 |
{% load i18n static %}<!DOCTYPE html> |
|
2 |
<html> |
|
3 |
<head> |
|
4 |
<meta charset="utf-8"/> |
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
6 |
<style> |
|
7 |
html, body { |
|
8 |
margin: 0; padding: 1rem; |
|
9 |
font-family: sans-serif; |
|
10 |
background: {{theme_color}}; |
|
11 |
} |
|
12 | ||
13 |
div.info-text { |
|
14 |
background: white; |
|
15 |
padding: 1rem; |
|
16 |
border-radius: 3px; |
|
17 |
} |
|
18 | ||
19 |
img { |
|
20 |
max-width: 100%; |
|
21 |
margin: 0 auto; |
|
22 |
display: block; |
|
23 |
} |
|
24 | ||
25 |
p.retry { |
|
26 |
margin-top: 2rem; |
|
27 |
text-align: center; |
|
28 |
} |
|
29 | ||
30 |
p.retry a { |
|
31 |
border: 1px solid {{theme_color}}; |
|
32 |
text-decoration: none; |
|
33 |
background: white; |
|
34 |
padding: 0.5rem 1rem; |
|
35 |
border-radius: 3px; |
|
36 |
color: inherit; |
|
37 |
} |
|
38 | ||
39 |
</style> |
|
40 |
</head> |
|
41 |
<body> |
|
42 |
<div class="info-text"> |
|
43 |
<img src="{% static "" %}{{ css_variant }}/{{ icon_prefix }}{{icon_sizes|last}}px.png" alt=""> |
|
44 |
{{ pwa_settings.offline_text|safe }} |
|
45 | ||
46 |
{% if pwa_settings.offline_retry_button %} |
|
47 |
<p class="retry"> |
|
48 |
<a href=".">{% trans "Retry" %}</a> |
|
49 |
</p> |
|
50 |
{% endif %} |
|
51 |
</div> |
|
52 |
</body> |
|
53 |
</html> |
combo/apps/pwa/templates/combo/service-worker.js | ||
---|---|---|
25 | 25 |
var config = { |
26 | 26 |
version: 'v{% start_timestamp %}', |
27 | 27 |
staticCacheItems: [ |
28 |
'/offline/' |
|
28 |
'/__pwa__/offline/'
|
|
29 | 29 |
], |
30 | 30 |
cachePathPattern: /^\/static\/.*/, |
31 | 31 |
handleFetchPathPattern: /.*/, |
32 |
offlinePage: '/offline/' |
|
32 |
offlinePage: '/__pwa__/offline/'
|
|
33 | 33 |
}; |
34 | 34 | |
35 | 35 |
function cacheName (key, opts) { |
combo/apps/pwa/urls.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from django.conf.urls import url |
|
17 |
from django.conf.urls import url, include
|
|
18 | 18 | |
19 |
from combo.urls_utils import decorated_includes, manager_required |
|
20 | ||
21 |
from .manager_views import ( |
|
22 |
ManagerHomeView, |
|
23 |
) |
|
19 | 24 |
from .views import ( |
20 | 25 |
manifest_json, |
21 | 26 |
service_worker_js, |
22 | 27 |
service_worker_registration_js, |
23 | 28 |
subscribe_push, |
29 |
offline_page, |
|
24 | 30 |
) |
25 | 31 | |
32 | ||
33 |
pwa_manager_urls = [ |
|
34 |
url('^$', ManagerHomeView.as_view(), name='pwa-manager-homepage'), |
|
35 |
] |
|
36 | ||
26 | 37 |
urlpatterns = [ |
27 | 38 |
url('^manifest.json$', manifest_json), |
28 | 39 |
url('^service-worker.js$', service_worker_js), |
29 | 40 |
url('^service-worker-registration.js$', service_worker_registration_js), |
30 | 41 |
url('^api/pwa/push/subscribe$', subscribe_push, name='pwa-subscribe-push'), |
42 |
url('^__pwa__/offline/$', offline_page), |
|
43 |
url(r'^manage/pwa/', decorated_includes(manager_required, |
|
44 |
include(pwa_manager_urls))), |
|
31 | 45 |
] |
combo/apps/pwa/views.py | ||
---|---|---|
21 | 21 |
from django.http import HttpResponse, HttpResponseForbidden, Http404, JsonResponse |
22 | 22 |
from django.template.loader import get_template, TemplateDoesNotExist |
23 | 23 |
from django.views.decorators.csrf import csrf_exempt |
24 |
from django.views.generic import TemplateView |
|
24 | 25 | |
25 |
from .models import PushSubscription |
|
26 |
from .models import PushSubscription, PwaSettings
|
|
26 | 27 | |
27 | 28 | |
28 | 29 |
def manifest_json(request, *args, **kwargs): |
... | ... | |
67 | 68 |
subscription_info=subscription_data) |
68 | 69 |
subscription.save() |
69 | 70 |
return JsonResponse({'err': 0}) |
71 | ||
72 | ||
73 |
class OfflinePage(TemplateView): |
|
74 |
template_name = 'combo/pwa/offline.html' |
|
75 | ||
76 |
def get_context_data(self, **kwargs): |
|
77 |
context = super(OfflinePage, self).get_context_data(**kwargs) |
|
78 |
context['pwa_settings'] = PwaSettings.singleton() |
|
79 |
return context |
|
80 | ||
81 |
offline_page = OfflinePage.as_view() |
combo/data/utils.py | ||
---|---|---|
22 | 22 | |
23 | 23 |
from combo.apps.assets.models import Asset |
24 | 24 |
from combo.apps.maps.models import MapLayer |
25 |
from combo.apps.pwa.models import PwaSettings |
|
25 | 26 |
from .models import Page |
26 | 27 | |
27 | 28 | |
... | ... | |
38 | 39 |
'''Dump site objects to JSON-dumpable dictionnary''' |
39 | 40 |
return {'pages': Page.export_all_for_json(), |
40 | 41 |
'map-layers': MapLayer.export_all_for_json(), |
41 |
'assets': Asset.export_all_for_json(),} |
|
42 |
'assets': Asset.export_all_for_json(), |
|
43 |
'pwa': { |
|
44 |
'settings': PwaSettings.export_for_json(), |
|
45 |
} |
|
46 |
} |
|
42 | 47 | |
43 | 48 | |
44 | 49 |
def import_site(data, if_empty=False, clean=False): |
... | ... | |
68 | 73 |
MapLayer.objects.all().delete() |
69 | 74 |
Asset.objects.all().delete() |
70 | 75 |
Page.objects.all().delete() |
76 |
PwaSettings.objects.all().delete() |
|
71 | 77 | |
72 | 78 |
with transaction.atomic(): |
73 | 79 |
MapLayer.load_serialized_objects(data.get('map-layers') or []) |
... | ... | |
77 | 83 | |
78 | 84 |
with transaction.atomic(): |
79 | 85 |
Page.load_serialized_pages(data.get('pages') or []) |
86 | ||
87 |
with transaction.atomic(): |
|
88 |
PwaSettings.load_serialized_settings((data.get('pwa') or {}).get('settings')) |
combo/settings.py | ||
---|---|---|
23 | 23 |
and to disable DEBUG mode in production. |
24 | 24 |
""" |
25 | 25 | |
26 |
import copy |
|
26 | 27 |
import os |
27 | 28 |
from django.conf import global_settings |
28 | 29 |
from django.utils.translation import ugettext_lazy as _ |
... | ... | |
177 | 178 |
}, |
178 | 179 |
} |
179 | 180 | |
181 |
CKEDITOR_CONFIGS['small'] = copy.copy(CKEDITOR_CONFIGS['default']) |
|
182 |
CKEDITOR_CONFIGS['small']['height'] = 150 |
|
183 | ||
180 | 184 |
HAYSTACK_CONNECTIONS = { |
181 | 185 |
'default': { |
182 | 186 |
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', |
tests/test_import_export.py | ||
---|---|---|
14 | 14 | |
15 | 15 |
from combo.apps.assets.models import Asset |
16 | 16 |
from combo.apps.maps.models import MapLayer, Map |
17 |
from combo.apps.pwa.models import PwaSettings |
|
17 | 18 |
from combo.data.models import Page, TextCell |
18 | 19 |
from combo.data.utils import export_site, import_site, MissingGroups |
19 | 20 | |
... | ... | |
198 | 199 | |
199 | 200 |
import_site(data={}, if_empty=True) |
200 | 201 |
assert Asset.objects.count() == 2 |
202 | ||
203 |
def test_import_export_pwa_settings(app): |
|
204 |
output = get_output_of_command('export_site') |
|
205 |
pwa_settings = PwaSettings.singleton() |
|
206 |
pwa_settings.offline_text = 'Hello world' |
|
207 |
pwa_settings.offline_retry_button = False |
|
208 |
pwa_settings.save() |
|
209 |
output = get_output_of_command('export_site') |
|
210 |
import_site(data={}, clean=True) |
|
211 |
assert PwaSettings.objects.all().count() == 0 |
|
212 | ||
213 |
import_site(data=json.loads(output)) |
|
214 |
assert PwaSettings.singleton().offline_retry_button is False |
|
215 |
assert PwaSettings.singleton().offline_text == 'Hello world' |
tests/test_pwa.py | ||
---|---|---|
13 | 13 |
from django.test import override_settings |
14 | 14 | |
15 | 15 |
from combo.apps.notifications.models import Notification |
16 |
from combo.apps.pwa.models import PushSubscription |
|
16 |
from combo.apps.pwa.models import PushSubscription, PwaSettings
|
|
17 | 17 | |
18 | 18 |
from .test_manager import login |
19 | 19 | |
... | ... | |
59 | 59 |
notification = Notification.notify(john_doe, 'test', body='hello world') |
60 | 60 |
assert webpush.call_count == 1 |
61 | 61 |
assert webpush.call_args[1]['subscription_info'] == {'sample': 'content'} |
62 | ||
63 |
def test_no_pwa_manager(app, admin_user): |
|
64 |
app = login(app) |
|
65 |
resp = app.get('/manage/', status=200) |
|
66 |
assert not '/manage/pwa/' in resp.text |
|
67 | ||
68 |
def test_pwa_manager(app, admin_user): |
|
69 |
app = login(app) |
|
70 |
with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}): |
|
71 |
resp = app.get('/manage/', status=200) |
|
72 |
assert '/manage/pwa/' in resp.text |
|
73 |
resp = app.get('/manage/pwa/') |
|
74 |
resp.form['offline_text'] = 'You are offline.' |
|
75 |
assert resp.form['offline_retry_button'].checked |
|
76 |
resp.form['offline_retry_button'].checked = False |
|
77 |
resp = resp.form.submit().follow() |
|
78 |
assert resp.form['offline_text'].value == 'You are offline.' |
|
79 |
assert resp.form['offline_retry_button'].checked is False |
|
80 | ||
81 |
def test_pwa_offline_page(app): |
|
82 |
PwaSettings.objects.all().delete() |
|
83 |
resp = app.get('/__pwa__/offline/') |
|
84 |
assert 'You are currently offline.' in resp.text |
|
85 |
assert 'Retry' in resp.text |
|
86 |
pwa_settings = PwaSettings.singleton() |
|
87 |
pwa_settings.offline_text = 'You are offline.' |
|
88 |
pwa_settings.offline_retry_button = False |
|
89 |
pwa_settings.save() |
|
90 | ||
91 |
resp = app.get('/__pwa__/offline/') |
|
92 |
assert 'You are offline.' in resp.text |
|
93 |
assert 'Retry' not in resp.text |
|
62 |
- |