pwa: add management of navigation entries (#29362)
This commit is contained in:
parent
1ebadd603c
commit
d9367275de
|
@ -15,9 +15,15 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.views.generic import UpdateView
|
||||
from django.db.models import Max
|
||||
from django import forms
|
||||
from django.http import JsonResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import CreateView, UpdateView, DeleteView
|
||||
|
||||
from .models import PwaSettings
|
||||
from combo.data.forms import get_page_choices
|
||||
|
||||
from .models import PwaSettings, PwaNavigationEntry
|
||||
|
||||
|
||||
class ManagerHomeView(UpdateView):
|
||||
|
@ -28,3 +34,59 @@ class ManagerHomeView(UpdateView):
|
|||
|
||||
def get_object(self):
|
||||
return PwaSettings.singleton()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ManagerHomeView, self).get_context_data(**kwargs)
|
||||
context['navigation_entries'] = PwaNavigationEntry.objects.all()
|
||||
return context
|
||||
|
||||
|
||||
class ManagerNavigationEntryMixin(object):
|
||||
model = PwaNavigationEntry
|
||||
fields = ['label', 'url', 'link_page', 'icon', 'extra_css_class',
|
||||
'notification_count', 'use_user_name_as_label']
|
||||
template_name = 'combo/pwa/manager_form.html'
|
||||
success_url = reverse_lazy('pwa-manager-homepage')
|
||||
|
||||
def get_form_class(self):
|
||||
form_class = forms.models.modelform_factory(self.model,
|
||||
fields=self.fields)
|
||||
form_class.base_fields['link_page'].choices = [(None, '-----')] + get_page_choices()
|
||||
return form_class
|
||||
|
||||
def form_valid(self, form):
|
||||
if form.instance.order is None:
|
||||
max_order = self.model.objects.all().aggregate(Max('order'))
|
||||
form.instance.order = (max_order['order__max'] or 0) + 1
|
||||
if form.instance.link_page_id is None:
|
||||
if not form.instance.label:
|
||||
form.add_error('label', _('A label is required when no page is selected.'))
|
||||
if not form.instance.url:
|
||||
form.add_error('url', _('An URL is required when no page is selected.'))
|
||||
elif form.instance.url:
|
||||
form.add_error('url', _('An URL cannot be specified when a page is selected.'))
|
||||
if form.errors:
|
||||
return super(ManagerNavigationEntryMixin, self).form_invalid(form)
|
||||
return super(ManagerNavigationEntryMixin, self).form_valid(form)
|
||||
|
||||
|
||||
class ManagerAddNavigationEntry(ManagerNavigationEntryMixin, CreateView):
|
||||
pass
|
||||
|
||||
|
||||
class ManagerEditNavigationEntry(ManagerNavigationEntryMixin, UpdateView):
|
||||
pass
|
||||
|
||||
|
||||
class ManagerDeleteNavigationEntry(DeleteView):
|
||||
model = PwaNavigationEntry
|
||||
success_url = reverse_lazy('pwa-manager-homepage')
|
||||
template_name = 'combo/generic_confirm_delete.html'
|
||||
|
||||
|
||||
def manager_navigation_order(request, *args, **kwargs):
|
||||
new_order = {int(x): i for i, x in enumerate(request.GET['new-order'].split(','))}
|
||||
for entry in PwaNavigationEntry.objects.all():
|
||||
entry.order = new_order[entry.id]
|
||||
entry.save()
|
||||
return JsonResponse({'err': 0})
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-12-27 14:27
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('data', '0036_page_sub_slug'),
|
||||
('pwa', '0002_pwasettings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PwaNavigationEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('label', models.CharField(blank=True, max_length=150, verbose_name='Label')),
|
||||
('url', models.CharField(blank=True, max_length=200, verbose_name='URL')),
|
||||
('icon', models.FileField(blank=True, null=True, upload_to=b'pwa', verbose_name='Icon')),
|
||||
('extra_css_class', models.CharField(blank=True, max_length=100, verbose_name='Extra classes for CSS styling')),
|
||||
('order', models.PositiveIntegerField()),
|
||||
('notification_count', models.BooleanField(default=False, verbose_name='Display notification count')),
|
||||
('use_user_name_as_label', models.BooleanField(default=False, verbose_name='Use user name as label')),
|
||||
('link_page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='data.Page', verbose_name='Internal link')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('order',),
|
||||
},
|
||||
),
|
||||
]
|
|
@ -14,15 +14,21 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import base64
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import serializers
|
||||
from django.core.files.storage import default_storage
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text, force_bytes
|
||||
from django.utils.six import BytesIO
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from jsonfield import JSONField
|
||||
from combo.data.fields import RichTextField
|
||||
from combo import utils
|
||||
|
||||
|
||||
class PwaSettings(models.Model):
|
||||
|
@ -56,6 +62,80 @@ class PwaSettings(models.Model):
|
|||
obj.save()
|
||||
|
||||
|
||||
class PwaNavigationEntry(models.Model):
|
||||
label = models.CharField(verbose_name=_('Label'), max_length=150, blank=True)
|
||||
url = models.CharField(verbose_name=_('External URL'), max_length=200, blank=True)
|
||||
link_page = models.ForeignKey('data.Page', blank=True,
|
||||
null=True, verbose_name=_('Internal link'))
|
||||
icon = models.FileField(_('Icon'), upload_to='pwa', blank=True, null=True)
|
||||
extra_css_class = models.CharField(_('Extra classes for CSS styling'), max_length=100, blank=True)
|
||||
order = models.PositiveIntegerField()
|
||||
|
||||
notification_count = models.BooleanField(
|
||||
verbose_name=_('Display notification count'),
|
||||
default=False)
|
||||
use_user_name_as_label = models.BooleanField(
|
||||
verbose_name=_('Use user name as label'),
|
||||
default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ('order',)
|
||||
|
||||
def get_label(self):
|
||||
return self.label or self.link_page.title
|
||||
|
||||
def get_url(self):
|
||||
if self.link_page:
|
||||
return self.link_page.get_online_url()
|
||||
else:
|
||||
return utils.get_templated_url(self.url)
|
||||
|
||||
def css_class_names(self):
|
||||
css_class_names = self.extra_css_class or ''
|
||||
if self.link_page:
|
||||
css_class_names += ' page-%s' % self.link_page.slug
|
||||
return css_class_names
|
||||
|
||||
@classmethod
|
||||
def export_all_for_json(cls):
|
||||
return [x.get_as_serialized_object() for x in cls.objects.all()]
|
||||
|
||||
def get_as_serialized_object(self):
|
||||
serialized_entry = json.loads(serializers.serialize('json', [self],
|
||||
use_natural_foreign_keys=True, use_natural_primary_keys=True))[0]
|
||||
if self.icon:
|
||||
encode = base64.encodestring if six.PY2 else base64.encodebytes
|
||||
serialized_entry['icon:base64'] = force_text(encode(self.icon.read()))
|
||||
del serialized_entry['model']
|
||||
del serialized_entry['pk']
|
||||
return serialized_entry
|
||||
|
||||
@classmethod
|
||||
def load_serialized_objects(cls, json_site):
|
||||
for json_entry in json_site:
|
||||
cls.load_serialized_object(json_entry)
|
||||
|
||||
@classmethod
|
||||
def load_serialized_object(cls, json_entry):
|
||||
json_entry['model'] = 'pwa.pwanavigationentry'
|
||||
# deserialize once to get link_page by natural key
|
||||
fake_entry = [x for x in serializers.deserialize('json', json.dumps([json_entry]))][0]
|
||||
entry, created = cls.objects.get_or_create(
|
||||
label=json_entry['fields']['label'],
|
||||
url=json_entry['fields']['url'],
|
||||
link_page=fake_entry.object.link_page,
|
||||
defaults={'order': 0})
|
||||
json_entry['pk'] = entry.id
|
||||
entry = [x for x in serializers.deserialize('json', json.dumps([json_entry]))][0]
|
||||
entry.save()
|
||||
if json_entry.get('icon:base64'):
|
||||
decode = base64.decodestring if six.PY2 else base64.decodebytes
|
||||
decoded_icon = decode(force_bytes(json_entry['icon:base64']))
|
||||
if not default_storage.exists(entry.object.icon.name) or entry.object.icon.read() != decoded_icon:
|
||||
# save new file
|
||||
entry.object.icon.save(entry.object.icon.name, BytesIO(decoded_icon))
|
||||
|
||||
|
||||
class PushSubscription(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||
subscription_info = JSONField()
|
||||
|
|
|
@ -82,3 +82,30 @@ div#mobile-case {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.section.navigation {
|
||||
ul.navigation-entries {
|
||||
margin-bottom: 0;
|
||||
+ ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
span.handle {
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 2em;
|
||||
+ a {
|
||||
padding-left: 4ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
li a.add {
|
||||
padding-left: 0;
|
||||
&::before {
|
||||
content: "\f055"; // circle-plus
|
||||
font-family: FontAwesome;
|
||||
width: 2.2em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "combo/pwa/manager_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2></h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Save" %}</button>
|
||||
<a class="cancel" href="{% url 'pwa-manager-homepage' %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -20,6 +20,36 @@
|
|||
|
||||
<div class="sections">
|
||||
|
||||
<div class="section navigation">
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<div>
|
||||
|
||||
{% if navigation_entries|length %}
|
||||
<p class="hint">
|
||||
{% blocktrans %}
|
||||
Use drag and drop with the ⣿ handles to reorder navigation entries.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="objects-list single-links navigation-entries"
|
||||
data-order-url="{% url 'pwa-manager-navigation-order' %}">
|
||||
{% for entry in navigation_entries %}
|
||||
<li data-pk="{{entry.pk}}"><span class="handle">⣿</span>
|
||||
<a rel="popup" href="{% url 'pwa-manager-navigation-edit' pk=entry.pk %}">{{ entry.get_label }}</a>
|
||||
<a rel="popup" class="delete" href="{% url 'pwa-manager-navigation-delete' pk=entry.pk %}">{% trans "remove" %}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if navigation_entries|length < 5 %}
|
||||
<ul class="objects-list single-links">
|
||||
<li><a class="add" rel="popup" href="{% url 'pwa-manager-navigation-add' %}">{% trans 'Add a navigation entry' %}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section settings">
|
||||
<h3>{% trans "Settings" %}</h3>
|
||||
<div>
|
||||
|
@ -48,6 +78,17 @@ $(function() {
|
|||
$('.mobile-app-content iframe').attr('src', '/');
|
||||
$('.mobile-app-content').addClass('splash-off');
|
||||
});
|
||||
|
||||
$('.navigation-entries').sortable({
|
||||
handle: '.handle',
|
||||
update: function(event, ui) {
|
||||
var new_order = $('.navigation-entries li').map(function() { return $(this).data('pk'); }).get().join();
|
||||
$.ajax({
|
||||
url: $('.navigation-entries').data('order-url'),
|
||||
data: {'new-order': new_order}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
{% load combo %}
|
||||
<div class="pwa-navigation" id="pwa-navigation">
|
||||
<div>
|
||||
<ul>
|
||||
{% for entry in entries %}
|
||||
<li class="{{ entry.css_class_names }}" data-entry-pk="{{ entry.pk }}"
|
||||
{% if entry.notification_count %}data-notification-count-url="{{site_base}}/api/notification/count/"{% endif %}
|
||||
{% 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 %}>
|
||||
<a href="{{ entry.get_url }}"
|
||||
{% if entry.icon %}style="background-image: url({{site_base}}{{entry.icon.url}});"{% endif %}
|
||||
><span>{{ entry.get_label }}</span></a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('li[data-pwa-user-name]').each(function(idx, elem) {
|
||||
var user_name = $(this).data('pwa-user-name');
|
||||
if (user_name) {
|
||||
$(this).find('span').text(user_name);
|
||||
}
|
||||
});
|
||||
$('body.authenticated-user li[data-notification-count-url]').each(function(idx, elem) {
|
||||
var $entry = $(this);
|
||||
$.ajax({
|
||||
url: $entry.data('notification-count-url'),
|
||||
xhrFields: { withCredentials: true },
|
||||
async: true,
|
||||
dataType: 'json',
|
||||
crossDomain: true,
|
||||
success: function(data) {
|
||||
if (data.new) {
|
||||
$entry.find('span').append(' <span class="badge">' + data.new + '</span>');
|
||||
}
|
||||
}});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,36 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2015-2018 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
from combo.apps.pwa.models import PwaNavigationEntry
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def pwa_navigation(context):
|
||||
if settings.TEMPLATE_VARS.get('pwa_display') not in ('standalone', 'fullscreen'):
|
||||
return ''
|
||||
pwa_navigation_template = template.loader.get_template('combo/pwa/navigation.html')
|
||||
context = {
|
||||
'entries': PwaNavigationEntry.objects.all(),
|
||||
'user': context.get('user'),
|
||||
'render_skeleton': context.get('render_skeleton'),
|
||||
'site_base': context['request'].build_absolute_uri('/')[:-1],
|
||||
}
|
||||
return pwa_navigation_template.render(context)
|
|
@ -20,6 +20,10 @@ from combo.urls_utils import decorated_includes, manager_required
|
|||
|
||||
from .manager_views import (
|
||||
ManagerHomeView,
|
||||
ManagerAddNavigationEntry,
|
||||
ManagerEditNavigationEntry,
|
||||
ManagerDeleteNavigationEntry,
|
||||
manager_navigation_order,
|
||||
)
|
||||
from .views import (
|
||||
manifest_json,
|
||||
|
@ -32,6 +36,18 @@ from .views import (
|
|||
|
||||
pwa_manager_urls = [
|
||||
url('^$', ManagerHomeView.as_view(), name='pwa-manager-homepage'),
|
||||
url('^navigation/add/$',
|
||||
ManagerAddNavigationEntry.as_view(),
|
||||
name='pwa-manager-navigation-add'),
|
||||
url('^navigation/edit/(?P<pk>\w+)/$',
|
||||
ManagerEditNavigationEntry.as_view(),
|
||||
name='pwa-manager-navigation-edit'),
|
||||
url('^navigation/delete/(?P<pk>\w+)/$',
|
||||
ManagerDeleteNavigationEntry.as_view(),
|
||||
name='pwa-manager-navigation-delete'),
|
||||
url('^navigation/order/$',
|
||||
manager_navigation_order,
|
||||
name='pwa-manager-navigation-order'),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -22,7 +22,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from combo.apps.assets.models import Asset
|
||||
from combo.apps.maps.models import MapLayer
|
||||
from combo.apps.pwa.models import PwaSettings
|
||||
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry
|
||||
from .models import Page
|
||||
|
||||
|
||||
|
@ -42,6 +42,7 @@ def export_site():
|
|||
'assets': Asset.export_all_for_json(),
|
||||
'pwa': {
|
||||
'settings': PwaSettings.export_for_json(),
|
||||
'navigation': PwaNavigationEntry.export_all_for_json(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +75,7 @@ def import_site(data, if_empty=False, clean=False):
|
|||
Asset.objects.all().delete()
|
||||
Page.objects.all().delete()
|
||||
PwaSettings.objects.all().delete()
|
||||
PwaNavigationEntry.objects.all().delete()
|
||||
|
||||
with transaction.atomic():
|
||||
MapLayer.load_serialized_objects(data.get('map-layers') or [])
|
||||
|
@ -84,5 +86,8 @@ def import_site(data, if_empty=False, clean=False):
|
|||
with transaction.atomic():
|
||||
Page.load_serialized_pages(data.get('pages') or [])
|
||||
|
||||
with transaction.atomic():
|
||||
PwaSettings.load_serialized_settings((data.get('pwa') or {}).get('settings'))
|
||||
if data.get('pwa'):
|
||||
with transaction.atomic():
|
||||
PwaSettings.load_serialized_settings(data['pwa'].get('settings'))
|
||||
with transaction.atomic():
|
||||
PwaNavigationEntry.load_serialized_objects(data['pwa'].get('navigation'))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
@ -9,12 +10,12 @@ import pytest
|
|||
from django.contrib.auth.models import Group
|
||||
from django.core.files import File
|
||||
from django.core.management import call_command
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils.six import BytesIO, StringIO
|
||||
|
||||
from combo.apps.assets.models import Asset
|
||||
from combo.apps.maps.models import MapLayer, Map
|
||||
from combo.apps.pwa.models import PwaSettings
|
||||
from combo.apps.pwa.models import PwaSettings, PwaNavigationEntry
|
||||
from combo.data.models import Page, TextCell
|
||||
from combo.data.utils import export_site, import_site, MissingGroups
|
||||
|
||||
|
@ -213,3 +214,39 @@ def test_import_export_pwa_settings(app):
|
|||
import_site(data=json.loads(output))
|
||||
assert PwaSettings.singleton().offline_retry_button is False
|
||||
assert PwaSettings.singleton().offline_text == 'Hello world'
|
||||
|
||||
def test_import_export_pwa_navigation(app, some_data):
|
||||
page = Page.objects.get(slug='one')
|
||||
entry1 = PwaNavigationEntry(label='a', url='/', order=0)
|
||||
entry2 = PwaNavigationEntry(link_page=page, order=1, icon=File(BytesIO(b'te\30st'), 'test.png'))
|
||||
entry1.save()
|
||||
entry2.save()
|
||||
output = get_output_of_command('export_site')
|
||||
import_site(data={}, clean=True)
|
||||
assert PwaNavigationEntry.objects.all().count() == 0
|
||||
|
||||
import_site(data=json.loads(output))
|
||||
assert PwaNavigationEntry.objects.all().count() == 2
|
||||
# check identical file was not touched
|
||||
assert os.path.basename(PwaNavigationEntry.objects.get(order=1).icon.file.name) == 'test.png'
|
||||
assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'te\30st'
|
||||
|
||||
# check a second import doesn't create additional entries
|
||||
import_site(data=json.loads(output))
|
||||
assert PwaNavigationEntry.objects.all().count() == 2
|
||||
|
||||
# check with a change in icon file content
|
||||
data = json.loads(output)
|
||||
data['pwa']['navigation'][1]['icon:base64'] = force_text(base64.encodestring(b'TEST'))
|
||||
import_site(data=data)
|
||||
assert PwaNavigationEntry.objects.all().count() == 2
|
||||
assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'TEST'
|
||||
|
||||
# check with a change in icon file name
|
||||
data = json.loads(output)
|
||||
data['pwa']['navigation'][1]['fields']['icon'] = 'pwa/test2.png'
|
||||
data['pwa']['navigation'][1]['icon:base64'] = force_text(base64.encodestring(b'TEST2'))
|
||||
import_site(data=data)
|
||||
assert PwaNavigationEntry.objects.all().count() == 2
|
||||
assert os.path.basename(PwaNavigationEntry.objects.get(order=1).icon.file.name) == 'test2.png'
|
||||
assert PwaNavigationEntry.objects.get(order=1).icon.read() == b'TEST2'
|
||||
|
|
|
@ -9,11 +9,16 @@ except ImportError:
|
|||
pywebpush = None
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import Context, Template
|
||||
from django.test import override_settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils.six import BytesIO
|
||||
|
||||
from combo.apps.notifications.models import Notification
|
||||
from combo.apps.pwa.models import PushSubscription, PwaSettings
|
||||
from combo.apps.pwa.models import PushSubscription, PwaSettings, PwaNavigationEntry
|
||||
from combo.data.models import Page
|
||||
|
||||
from .test_manager import login
|
||||
|
||||
|
@ -78,6 +83,64 @@ def test_pwa_manager(app, admin_user):
|
|||
assert resp.form['offline_text'].value == 'You are offline.'
|
||||
assert resp.form['offline_retry_button'].checked is False
|
||||
|
||||
resp = app.get('/manage/pwa/')
|
||||
resp = resp.click('Add a navigation entry')
|
||||
resp.form['label'] = 'Hello'
|
||||
resp.form['url'] = 'https://www.example.net'
|
||||
resp = resp.form.submit().follow()
|
||||
assert PwaNavigationEntry.objects.all().count() == 1
|
||||
|
||||
page = Page(title='test', slug='test')
|
||||
page.save()
|
||||
|
||||
resp = resp.click('Add a navigation entry')
|
||||
resp.form['link_page'] = page.id
|
||||
resp = resp.form.submit().follow()
|
||||
assert PwaNavigationEntry.objects.all().count() == 2
|
||||
|
||||
for i in range(3):
|
||||
resp = resp.click('Add a navigation entry')
|
||||
resp.form['label'] = 'Hello %s' % i
|
||||
resp.form['url'] = 'https://www.example.net'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
# max 5 items
|
||||
assert 'Add a navigation entry' not in resp.text
|
||||
|
||||
# reorder items, reverse them all
|
||||
entries = PwaNavigationEntry.objects.all()
|
||||
app.get('/manage/pwa/navigation/order/?new-order=%s' %
|
||||
','.join(reversed([str(x.id) for x in entries])))
|
||||
entries = PwaNavigationEntry.objects.all()
|
||||
assert entries[0].label == 'Hello 2'
|
||||
|
||||
# remove first item
|
||||
resp = app.get('/manage/pwa/')
|
||||
resp = resp.click(href='delete', index=0)
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Hello 2' not in resp.text
|
||||
assert 'Add a navigation entry' in resp.text
|
||||
|
||||
# rename item
|
||||
resp = resp.click('Hello 1')
|
||||
resp.form['label'] = 'Hello 12'
|
||||
resp = resp.form.submit().follow()
|
||||
assert PwaNavigationEntry.objects.all()[0].label == 'Hello 12'
|
||||
|
||||
# check error handling
|
||||
resp = resp.click('Hello 12')
|
||||
resp.form['label'] = ''
|
||||
resp.form['url'] = ''
|
||||
resp = resp.form.submit()
|
||||
assert 'A label is required' in resp.text
|
||||
assert 'An URL is required' in resp.text
|
||||
|
||||
resp.form['url'] = 'foobar'
|
||||
resp.form['link_page'] = page.id
|
||||
resp = resp.form.submit()
|
||||
assert 'An URL cannot be specified' in resp.text
|
||||
|
||||
|
||||
def test_pwa_offline_page(app):
|
||||
PwaSettings.objects.all().delete()
|
||||
resp = app.get('/__pwa__/offline/')
|
||||
|
@ -91,3 +154,27 @@ def test_pwa_offline_page(app):
|
|||
resp = app.get('/__pwa__/offline/')
|
||||
assert 'You are offline.' in resp.text
|
||||
assert 'Retry' not in resp.text
|
||||
|
||||
|
||||
def test_pwa_navigation_templatetag(app):
|
||||
page = Page(title='One', slug='one')
|
||||
page.save()
|
||||
entry1 = PwaNavigationEntry(label='a', url='/', notification_count=True,
|
||||
use_user_name_as_label=True, order=0)
|
||||
entry2 = PwaNavigationEntry(link_page=page, order=1, icon=File(BytesIO(b'te\30st'), 'test.png'))
|
||||
entry1.save()
|
||||
entry2.save()
|
||||
t = Template('{% load pwa %}{% pwa_navigation %}')
|
||||
assert t.render(Context({})) == ''
|
||||
|
||||
with override_settings(TEMPLATE_VARS={'pwa_display': 'standalone'}):
|
||||
request = RequestFactory().get('/')
|
||||
nav = t.render(Context({'request': request}))
|
||||
assert '<span>a</span>' in nav
|
||||
assert '<span>One</span>' in nav
|
||||
assert nav.count('background-image') == 1
|
||||
assert nav.count('data-notification-count-url=') == 1
|
||||
assert nav.count('data-pwa-user-name=""') == 1
|
||||
|
||||
nav = t.render(Context({'request': request, 'render_skeleton': True}))
|
||||
assert 'data-pwa-user-name="{% block placeholder-user-name %}' in nav
|
||||
|
|
Loading…
Reference in New Issue