Projet

Général

Profil

0001-pwa-add-management-of-navigation-entries-29362.patch

Frédéric Péters, 28 décembre 2018 16:24

Télécharger (26,7 ko)

Voir les différences:

Subject: [PATCH] pwa: add management of navigation entries (#29362)

 combo/apps/pwa/manager_views.py               | 64 +++++++++++++-
 .../pwa/migrations/0003_pwanavigationentry.py | 34 ++++++++
 combo/apps/pwa/models.py                      | 79 +++++++++++++++++
 .../pwa/static/css/combo.manager.pwa.scss     | 27 ++++++
 .../pwa/templates/combo/pwa/manager_form.html | 17 ++++
 .../pwa/templates/combo/pwa/manager_home.html | 41 +++++++++
 .../pwa/templates/combo/pwa/navigation.html   | 37 ++++++++
 combo/apps/pwa/templatetags/__init__.py       |  0
 combo/apps/pwa/templatetags/pwa.py            | 36 ++++++++
 combo/apps/pwa/urls.py                        | 16 ++++
 combo/data/utils.py                           | 11 ++-
 tests/test_import_export.py                   | 41 ++++++++-
 tests/test_pwa.py                             | 84 ++++++++++++++++++-
 13 files changed, 479 insertions(+), 8 deletions(-)
 create mode 100644 combo/apps/pwa/migrations/0003_pwanavigationentry.py
 create mode 100644 combo/apps/pwa/templates/combo/pwa/manager_form.html
 create mode 100644 combo/apps/pwa/templates/combo/pwa/navigation.html
 create mode 100644 combo/apps/pwa/templatetags/__init__.py
 create mode 100644 combo/apps/pwa/templatetags/pwa.py
combo/apps/pwa/manager_views.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from django.core.urlresolvers import reverse_lazy
18
from django.views.generic import UpdateView
18
from django.db.models import Max
19
from django import forms
20
from django.http import JsonResponse
21
from django.utils.translation import ugettext_lazy as _
22
from django.views.generic import CreateView, UpdateView, DeleteView
19 23

  
20
from .models import PwaSettings
24
from combo.data.forms import get_page_choices
25

  
26
from .models import PwaSettings, PwaNavigationEntry
21 27

  
22 28

  
23 29
class ManagerHomeView(UpdateView):
......
28 34

  
29 35
    def get_object(self):
30 36
        return PwaSettings.singleton()
37

  
38
    def get_context_data(self, **kwargs):
39
        context = super(ManagerHomeView, self).get_context_data(**kwargs)
40
        context['navigation_entries'] = PwaNavigationEntry.objects.all()
41
        return context
42

  
43

  
44
class ManagerNavigationEntryMixin(object):
45
    model = PwaNavigationEntry
46
    fields = ['label', 'url', 'link_page', 'icon', 'extra_css_class',
47
              'notification_count', 'use_user_name_as_label']
48
    template_name = 'combo/pwa/manager_form.html'
49
    success_url = reverse_lazy('pwa-manager-homepage')
50

  
51
    def get_form_class(self):
52
        form_class = forms.models.modelform_factory(self.model,
53
                fields=self.fields)
54
        form_class.base_fields['link_page'].choices = [(None, '-----')] + get_page_choices()
55
        return form_class
56

  
57
    def form_valid(self, form):
58
        if form.instance.order is None:
59
            max_order = self.model.objects.all().aggregate(Max('order'))
60
            form.instance.order = (max_order['order__max'] or 0) + 1
61
        if form.instance.link_page_id is None:
62
            if not form.instance.label:
63
                form.add_error('label', _('A label is required when no page is selected.'))
64
            if not form.instance.url:
65
                form.add_error('url', _('An URL is required when no page is selected.'))
66
            if form.errors:
67
                return super(ManagerNavigationEntryMixin, self).form_invalid(form)
68
        return super(ManagerNavigationEntryMixin, self).form_valid(form)
69

  
70

  
71
class ManagerAddNavigationEntry(ManagerNavigationEntryMixin, CreateView):
72
    pass
73

  
74

  
75
class ManagerEditNavigationEntry(ManagerNavigationEntryMixin, UpdateView):
76
    pass
77

  
78

  
79
class ManagerDeleteNavigationEntry(DeleteView):
80
    model = PwaNavigationEntry
81
    success_url = reverse_lazy('pwa-manager-homepage')
82
    template_name = 'combo/generic_confirm_delete.html'
83

  
84

  
85
def manager_navigation_order(request, *args, **kwargs):
86
    new_order = {int(x): i for i, x in enumerate(request.GET['new-order'].split(','))}
87
    for entry in PwaNavigationEntry.objects.all():
88
        entry.order = new_order[entry.id]
89
        entry.save()
90
    return JsonResponse({'err': 0})
combo/apps/pwa/migrations/0003_pwanavigationentry.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.12 on 2018-12-27 14:27
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import django.db.models.deletion
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('data', '0036_page_sub_slug'),
13
        ('pwa', '0002_pwasettings'),
14
    ]
15

  
16
    operations = [
17
        migrations.CreateModel(
18
            name='PwaNavigationEntry',
19
            fields=[
20
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
                ('label', models.CharField(blank=True, max_length=150, verbose_name='Label')),
22
                ('url', models.CharField(blank=True, max_length=200, verbose_name='URL')),
23
                ('icon', models.FileField(blank=True, null=True, upload_to=b'pwa')),
24
                ('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
25
                ('order', models.PositiveIntegerField()),
26
                ('notification_count', models.BooleanField(default=False, verbose_name='Display notification count')),
27
                ('use_user_name_as_label', models.BooleanField(default=False, verbose_name='Use user name as label')),
28
                ('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='data.Page', verbose_name='Internal link')),
29
            ],
30
            options={
31
                'ordering': ('order',),
32
            },
33
        ),
34
    ]
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 base64
17 18
import json
18 19

  
19 20
from django.conf import settings
20 21
from django.core import serializers
22
from django.core.files.storage import default_storage
21 23
from django.db import models
24
from django.utils import six
25
from django.utils.encoding import force_text, force_bytes
26
from django.utils.six import BytesIO
22 27
from django.utils.translation import ugettext_lazy as _
23 28

  
24 29
from jsonfield import JSONField
25 30
from combo.data.fields import RichTextField
31
from combo import utils
26 32

  
27 33

  
28 34
class PwaSettings(models.Model):
......
56 62
        obj.save()
57 63

  
58 64

  
65
class PwaNavigationEntry(models.Model):
66
    label = models.CharField(verbose_name=_('Label'), max_length=150, blank=True)
67
    url = models.CharField(verbose_name=_('External URL'), max_length=200, blank=True)
68
    link_page = models.ForeignKey('data.Page', blank=True,
69
            null=True, verbose_name=_('Internal link'))
70
    icon = models.FileField(upload_to='pwa', blank=True, null=True)
71
    extra_css_class = models.CharField(_('Extra classes for CSS styling'), max_length=100, blank=True)
72
    order = models.PositiveIntegerField()
73

  
74
    notification_count = models.BooleanField(
75
            verbose_name=_('Display notification count'),
76
            default=False)
77
    use_user_name_as_label = models.BooleanField(
78
            verbose_name=_('Use user name as label'),
79
            default=False)
80

  
81
    class Meta:
82
        ordering = ('order',)
83

  
84
    def get_label(self):
85
        return self.label or self.link_page.title
86

  
87
    def get_url(self):
88
        if self.url:
89
            return utils.get_templated_url(self.url)
90
        return self.link_page.get_online_url()
91

  
92
    def css_class_names(self):
93
        css_class_names = self.extra_css_class or ''
94
        if self.link_page:
95
            css_class_names += ' page-%s' % self.link_page.slug
96
        return css_class_names
97

  
98
    @classmethod
99
    def export_all_for_json(cls):
100
        return [x.get_as_serialized_object() for x in cls.objects.all()]
101

  
102
    def get_as_serialized_object(self):
103
        serialized_entry = json.loads(serializers.serialize('json', [self],
104
            use_natural_foreign_keys=True, use_natural_primary_keys=True))[0]
105
        if self.icon:
106
            encode = base64.encodestring if six.PY2 else base64.encodebytes
107
            serialized_entry['icon:base64'] = force_text(encode(self.icon.read()))
108
        del serialized_entry['model']
109
        del serialized_entry['pk']
110
        return serialized_entry
111

  
112
    @classmethod
113
    def load_serialized_objects(cls, json_site):
114
        for json_entry in json_site:
115
            cls.load_serialized_object(json_entry)
116

  
117
    @classmethod
118
    def load_serialized_object(cls, json_entry):
119
        json_entry['model'] = 'pwa.pwanavigationentry'
120
        # deserialize once to get link_page by natural key
121
        fake_entry = [x for x in serializers.deserialize('json', json.dumps([json_entry]))][0]
122
        entry, created = cls.objects.get_or_create(
123
                label=json_entry['fields']['label'],
124
                url=json_entry['fields']['url'],
125
                link_page=fake_entry.object.link_page,
126
                defaults={'order': 0})
127
        json_entry['pk'] = entry.id
128
        entry = [x for x in serializers.deserialize('json', json.dumps([json_entry]))][0]
129
        entry.save()
130
        if json_entry.get('icon:base64'):
131
            decode = base64.decodestring if six.PY2 else base64.decodebytes
132
            decoded_icon = decode(force_bytes(json_entry['icon:base64']))
133
            if not default_storage.exists(entry.object.icon.name) or entry.object.icon.read() != decoded_icon:
134
                # save new file
135
                entry.object.icon.save(entry.object.icon.name, BytesIO(decoded_icon))
136

  
137

  
59 138
class PushSubscription(models.Model):
60 139
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
61 140
    subscription_info = JSONField()
combo/apps/pwa/static/css/combo.manager.pwa.scss
82 82
		}
83 83
	}
84 84
}
85

  
86
div.section.navigation {
87
	ul.navigation-entries {
88
		margin-bottom: 0;
89
		+ ul {
90
			margin-top: 0;
91
		}
92
		span.handle {
93
			padding: 0;
94
			position: absolute;
95
			width: 2em;
96
			+ a {
97
				padding-left: 4ex;
98
			}
99
		}
100
	}
101
	li a.add {
102
		padding-left: 0;
103
		&::before {
104
			content: "\f055"; // circle-plus
105
			font-family: FontAwesome;
106
			width: 2.2em;
107
			display: inline-block;
108
			text-align: center;
109
		}
110
	}
111
}
combo/apps/pwa/templates/combo/pwa/manager_form.html
1
{% extends "combo/pwa/manager_base.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
<h2></h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post" enctype="multipart/form-data">
10
  {% csrf_token %}
11
  {{ form.as_p }}
12
  <div class="buttons">
13
    <button class="submit-button">{% trans "Save" %}</button>
14
    <a class="cancel" href="{% url 'pwa-manager-homepage' %}">{% trans 'Cancel' %}</a>
15
  </div>
16
</form>
17
{% endblock %}
combo/apps/pwa/templates/combo/pwa/manager_home.html
20 20

  
21 21
<div class="sections">
22 22

  
23
<div class="section navigation">
24
<h3>{% trans "Navigation" %}</h3>
25
<div>
26

  
27
{% if navigation_entries|length %}
28
<p class="hint">
29
{% blocktrans %}
30
Use drag and drop with the ⣿ handles to reorder navigation entries.
31
{% endblocktrans %}
32
</p>
33
{% endif %}
34

  
35
<ul class="objects-list single-links navigation-entries"
36
    data-order-url="{% url 'pwa-manager-navigation-order' %}">
37
{% for entry in navigation_entries %}
38
<li data-pk="{{entry.pk}}"><span class="handle">⣿</span>
39
    <a rel="popup" href="{% url 'pwa-manager-navigation-edit' pk=entry.pk %}">{{ entry.get_label }}</a>
40
    <a rel="popup" class="delete" href="{% url 'pwa-manager-navigation-delete' pk=entry.pk %}">{% trans "remove" %}</a>
41
</li>
42
{% endfor %}
43
</ul>
44
{% if navigation_entries|length < 5 %}
45
<ul class="objects-list single-links">
46
<li><a class="add" rel="popup" href="{% url 'pwa-manager-navigation-add' %}">{% trans 'Add a navigation entry' %}</a></li>
47
</ul>
48
{% endif %}
49

  
50
</div>
51
</div>
52

  
23 53
<div class="section settings">
24 54
<h3>{% trans "Settings" %}</h3>
25 55
<div>
......
48 78
    $('.mobile-app-content iframe').attr('src', '/');
49 79
    $('.mobile-app-content').addClass('splash-off');
50 80
  });
81

  
82
  $('.navigation-entries').sortable({
83
    handle: '.handle',
84
    update: function(event, ui) {
85
      var new_order = $('.navigation-entries li').map(function() { return $(this).data('pk'); }).get().join();
86
      $.ajax({
87
         url: $('.navigation-entries').data('order-url'),
88
           data: {'new-order': new_order}
89
       });
90
    }
91
  });
51 92
});
52 93
</script>
53 94

  
combo/apps/pwa/templates/combo/pwa/navigation.html
1
{% load combo %}
2
<div class="pwa-navigation" id="pwa-navigation">
3
<div>
4
<ul>
5
  {% for entry in entries %}
6
  <li class="{{ entry.css_class_names }}" data-entry-pk="{{ entry.pk }}"
7
      {% if entry.notification_count %}data-notification-count-url="{{site_base}}/api/notification/count/"{% endif %}
8
      {% if entry.use_user_name_as_label %}data-pwa-user-name="{% skeleton_extra_placeholder user-name %}{{user.get_full_name}}{% end_skeleton_extra_placeholder %}"{% endif %}>
9
      <a href="{{ entry.get_url }}"
10
         {% if entry.icon %}style="background-image: url({{site_base}}{{entry.icon.url}});"{% endif %}
11
         ><span>{{ entry.get_label }}</span></a></li>
12
  {% endfor %}
13
</ul>
14
</div>
15
</div>
16
<script>
17
$('li[data-pwa-user-name]').each(function(idx, elem) {
18
  var user_name = $(this).data('pwa-user-name');
19
  if (user_name) {
20
    $(this).find('span').text(user_name);
21
  }
22
});
23
$('body.authenticated-user li[data-notification-count-url]').each(function(idx, elem) {
24
  var $entry = $(this);
25
  $.ajax({
26
    url: $entry.data('notification-count-url'),
27
    xhrFields: { withCredentials: true },
28
    async: true,
29
    dataType: 'json',
30
    crossDomain: true,
31
    success: function(data) {
32
      if (data.new) {
33
        $entry.find('span').append(' <span class="badge">' + data.new + '</span>');
34
      }
35
  }});
36
});
37
</script>
combo/apps/pwa/templatetags/pwa.py
1
# combo - content management system
2
# Copyright (C) 2015-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 import template
18
from django.conf import settings
19

  
20
from combo.apps.pwa.models import PwaNavigationEntry
21

  
22

  
23
register = template.Library()
24

  
25
@register.simple_tag(takes_context=True)
26
def pwa_navigation(context):
27
    if settings.TEMPLATE_VARS.get('pwa_display') not in ('standalone', 'fullscreen'):
28
        return ''
29
    pwa_navigation_template = template.loader.get_template('combo/pwa/navigation.html')
30
    context = {
31
        'entries': PwaNavigationEntry.objects.all(),
32
        'user': context.get('user'),
33
        'render_skeleton': context.get('render_skeleton'),
34
        'site_base': context['request'].build_absolute_uri('/')[:-1],
35
    }
36
    return pwa_navigation_template.render(context)
combo/apps/pwa/urls.py
20 20

  
21 21
from .manager_views import (
22 22
        ManagerHomeView,
23
        ManagerAddNavigationEntry,
24
        ManagerEditNavigationEntry,
25
        ManagerDeleteNavigationEntry,
26
        manager_navigation_order,
23 27
        )
24 28
from .views import (
25 29
        manifest_json,
......
32 36

  
33 37
pwa_manager_urls = [
34 38
    url('^$', ManagerHomeView.as_view(), name='pwa-manager-homepage'),
39
    url('^navigation/add/$',
40
        ManagerAddNavigationEntry.as_view(),
41
        name='pwa-manager-navigation-add'),
42
    url('^navigation/edit/(?P<pk>\w+)/$',
43
        ManagerEditNavigationEntry.as_view(),
44
        name='pwa-manager-navigation-edit'),
45
    url('^navigation/delete/(?P<pk>\w+)/$',
46
        ManagerDeleteNavigationEntry.as_view(),
47
        name='pwa-manager-navigation-delete'),
48
    url('^navigation/order/$',
49
        manager_navigation_order,
50
        name='pwa-manager-navigation-order'),
35 51
]
36 52

  
37 53
urlpatterns = [
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
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry
26 26
from .models import Page
27 27

  
28 28

  
......
42 42
            'assets': Asset.export_all_for_json(),
43 43
            'pwa': {
44 44
                'settings': PwaSettings.export_for_json(),
45
                'navigation': PwaNavigationEntry.export_all_for_json(),
45 46
            }
46 47
           }
47 48

  
......
74 75
        Asset.objects.all().delete()
75 76
        Page.objects.all().delete()
76 77
        PwaSettings.objects.all().delete()
78
        PwaNavigationEntry.objects.all().delete()
77 79

  
78 80
    with transaction.atomic():
79 81
        MapLayer.load_serialized_objects(data.get('map-layers') or [])
......
84 86
    with transaction.atomic():
85 87
        Page.load_serialized_pages(data.get('pages') or [])
86 88

  
87
    with transaction.atomic():
88
        PwaSettings.load_serialized_settings((data.get('pwa') or {}).get('settings'))
89
    if data.get('pwa'):
90
        with transaction.atomic():
91
            PwaSettings.load_serialized_settings(data['pwa'].get('settings'))
92
        with transaction.atomic():
93
            PwaNavigationEntry.load_serialized_objects(data['pwa'].get('navigation'))
tests/test_import_export.py
1
import base64
1 2
import datetime
2 3
import json
3 4
import os
......
9 10
from django.contrib.auth.models import Group
10 11
from django.core.files import File
11 12
from django.core.management import call_command
12
from django.utils.encoding import force_bytes
13
from django.utils.encoding import force_bytes, force_text
13 14
from django.utils.six import BytesIO, StringIO
14 15

  
15 16
from combo.apps.assets.models import Asset
16 17
from combo.apps.maps.models import MapLayer, Map
17
from combo.apps.pwa.models import PwaSettings
18
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry
18 19
from combo.data.models import Page, TextCell
19 20
from combo.data.utils import export_site, import_site, MissingGroups
20 21

  
......
213 214
    import_site(data=json.loads(output))
214 215
    assert PwaSettings.singleton().offline_retry_button is False
215 216
    assert PwaSettings.singleton().offline_text == 'Hello world'
217

  
218
def test_import_export_pwa_navigation(app, some_data):
219
    page = Page.objects.get(slug='one')
220
    entry1 = PwaNavigationEntry(label='a', url='/', order=0)
221
    entry2 = PwaNavigationEntry(link_page=page, order=1, icon=File(BytesIO(b'te\30st'), 'test.png'))
222
    entry1.save()
223
    entry2.save()
224
    output = get_output_of_command('export_site')
225
    import_site(data={}, clean=True)
226
    assert PwaNavigationEntry.objects.all().count() == 0
227

  
228
    import_site(data=json.loads(output))
229
    assert PwaNavigationEntry.objects.all().count() == 2
230
    # check identical file was not touched
231
    assert os.path.basename(PwaNavigationEntry.objects.get(order=1).icon.file.name) == 'test.png'
232
    assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'te\30st'
233

  
234
    # check a second import doesn't create additional entries
235
    import_site(data=json.loads(output))
236
    assert PwaNavigationEntry.objects.all().count() == 2
237

  
238
    # check with a change in icon file content
239
    data = json.loads(output)
240
    data['pwa']['navigation'][1]['icon:base64'] = force_text(base64.encodestring(b'TEST'))
241
    import_site(data=data)
242
    assert PwaNavigationEntry.objects.all().count() == 2
243
    assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'TEST'
244

  
245
    # check with a change in icon file name
246
    data = json.loads(output)
247
    data['pwa']['navigation'][1]['fields']['icon'] = 'pwa/test2.png'
248
    data['pwa']['navigation'][1]['icon:base64'] = force_text(base64.encodestring(b'TEST2'))
249
    import_site(data=data)
250
    assert PwaNavigationEntry.objects.all().count() == 2
251
    assert os.path.basename(PwaNavigationEntry.objects.get(order=1).icon.file.name) == 'test2.png'
252
    assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'TEST2'
tests/test_pwa.py
9 9
    pywebpush = None
10 10

  
11 11
from django.conf import settings
12
from django.core.files import File
12 13
from django.core.urlresolvers import reverse
14
from django.template import Context, Template
13 15
from django.test import override_settings
16
from django.test.client import RequestFactory
17
from django.utils.six import BytesIO
14 18

  
15 19
from combo.apps.notifications.models import Notification
16
from combo.apps.pwa.models import PushSubscription, PwaSettings
20
from combo.apps.pwa.models import PushSubscription, PwaSettings, PwaNavigationEntry
21
from combo.data.models import Page
17 22

  
18 23
from .test_manager import login
19 24

  
......
78 83
        assert resp.form['offline_text'].value == 'You are offline.'
79 84
        assert resp.form['offline_retry_button'].checked is False
80 85

  
86
        resp = app.get('/manage/pwa/')
87
        resp = resp.click('Add a navigation entry')
88
        resp.form['label'] = 'Hello'
89
        resp.form['url'] = 'https://www.example.net'
90
        resp = resp.form.submit().follow()
91
        assert PwaNavigationEntry.objects.all().count() == 1
92

  
93
        page = Page(title='test', slug='test')
94
        page.save()
95

  
96
        resp = resp.click('Add a navigation entry')
97
        resp.form['link_page'] = page.id
98
        resp = resp.form.submit().follow()
99
        assert PwaNavigationEntry.objects.all().count() == 2
100

  
101
        for i in range(3):
102
            resp = resp.click('Add a navigation entry')
103
            resp.form['label'] = 'Hello %s' % i
104
            resp.form['url'] = 'https://www.example.net'
105
            resp = resp.form.submit().follow()
106

  
107
        # max 5 items
108
        assert 'Add a navigation entry' not in resp.text
109

  
110
        # reorder items, reverse them all
111
        entries = PwaNavigationEntry.objects.all()
112
        app.get('/manage/pwa/navigation/order/?new-order=%s' %
113
                ','.join(reversed([str(x.id) for x in entries])))
114
        entries = PwaNavigationEntry.objects.all()
115
        assert entries[0].label == 'Hello 2'
116

  
117
        # remove first item
118
        resp = app.get('/manage/pwa/')
119
        resp = resp.click(href='delete', index=0)
120
        resp = resp.form.submit().follow()
121
        assert 'Hello 2' not in resp.text
122
        assert 'Add a navigation entry' in resp.text
123

  
124
        # rename item
125
        resp = resp.click('Hello 1')
126
        resp.form['label'] = 'Hello 12'
127
        resp = resp.form.submit().follow()
128
        assert PwaNavigationEntry.objects.all()[0].label == 'Hello 12'
129

  
130
        # check error handling
131
        resp = resp.click('Hello 12')
132
        resp.form['label'] = ''
133
        resp.form['url'] = ''
134
        resp = resp.form.submit()
135
        assert 'A label is required' in resp.text
136
        assert 'An URL is required' in resp.text
137

  
138

  
81 139
def test_pwa_offline_page(app):
82 140
    PwaSettings.objects.all().delete()
83 141
    resp = app.get('/__pwa__/offline/')
......
91 149
    resp = app.get('/__pwa__/offline/')
92 150
    assert 'You are offline.' in resp.text
93 151
    assert 'Retry' not in resp.text
152

  
153

  
154
def test_pwa_navigation_templatetag(app):
155
    page = Page(title='One', slug='one')
156
    page.save()
157
    entry1 = PwaNavigationEntry(label='a', url='/', notification_count=True,
158
            use_user_name_as_label=True, order=0)
159
    entry2 = PwaNavigationEntry(link_page=page, order=1, icon=File(BytesIO(b'te\30st'), 'test.png'))
160
    entry1.save()
161
    entry2.save()
162
    t = Template('{% load pwa %}{% pwa_navigation %}')
163
    assert t.render(Context({})) == ''
164

  
165
    with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}):
166
        request = RequestFactory().get('/')
167
        nav = t.render(Context({'request': request}))
168
        assert '<span>a</span>' in nav
169
        assert '<span>One</span>' in nav
170
        assert nav.count('background-image') == 1
171
        assert nav.count('data-notification-count-url=') == 1
172
        assert nav.count('data-pwa-user-name=""') == 1
173

  
174
        nav = t.render(Context({'request': request, 'render_skeleton': True}))
175
        assert 'data-pwa-user-name="{% block placeholder-user-name %}' in nav
94
-