web push notifications : added new templatetag, javascript static, model, a mgmt command and tests (#22727)
This commit is contained in:
parent
266ede325f
commit
3efc1a387d
|
@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
class Plugin(object):
|
||||
def get_apps(self):
|
||||
return [__name__]
|
||||
return [__name__, 'webpush']
|
||||
|
||||
|
||||
class AppConfig(django.apps.AppConfig):
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# combo-plugin-gnm - Combo WebPush App
|
||||
# Copyright (C) 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 dateutil.parser import parse
|
||||
from datetime import timedelta, datetime
|
||||
import argparse
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import make_aware, now
|
||||
from combo.apps.notifications.models import Notification
|
||||
from combo_plugin_gnm.push import send_web_push
|
||||
|
||||
|
||||
|
||||
|
||||
def to_datetime_argument(string):
|
||||
try:
|
||||
return make_aware(parse(string))
|
||||
except ValueError as verr:
|
||||
raise argparse.ArgumentTypeError(verr)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = u'''Send push notification for the last notifications, for example :
|
||||
> python manage.py send_webpush --since=2018-03-03T00:00:00
|
||||
will all waiting web push notifications from march 3rd 2018 until now
|
||||
'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Named (optional) arguments
|
||||
parser.add_argument(
|
||||
'--since',
|
||||
dest='since',
|
||||
default=None,
|
||||
type=to_datetime_argument,
|
||||
help='Send push notification created since this datetime',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
''' Send visible and new web-push notifications
|
||||
'''
|
||||
since = options.get('since')
|
||||
if since is None:
|
||||
since = now() - timedelta(days=1)
|
||||
|
||||
verbosity = options.get('verbosity')
|
||||
notif_query = Notification.objects.new().visible().filter(start_timestamp__gte=since)
|
||||
for notif in notif_query:
|
||||
if verbosity > 0:
|
||||
print('Pushing notification %s' % str(notif).decode('utf-8'))
|
||||
answer = [str(response_obj) for response_obj in send_web_push(notif, since=since)]
|
||||
if verbosity > 0:
|
||||
print('Web Push Responses : %s' % " | ".join(answer))
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('webpush', '0001_initial'),
|
||||
('notifications', '0004_auto_20180316_1026'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WebPushRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('creation_date', models.DateTimeField(auto_now_add=True)),
|
||||
('status', models.CharField(default=b'NEW', max_length=4, blank=True, choices=[(b'NEW', b'Not-sent notification'), (b'SENT', b'Sent notification'), (b'ERR', b'Invalid notification')])),
|
||||
('notification', models.ForeignKey(to='notifications.Notification')),
|
||||
('subscription', models.ForeignKey(to='webpush.PushInformation')),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='webpushrecord',
|
||||
unique_together=set([('subscription', 'notification')]),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# combo-plugin-gnm - Combo WebPush App
|
||||
# Copyright (C) 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.db import models
|
||||
|
||||
# TODO use signals to hook Notification model create or update ?
|
||||
from combo.apps.notifications.models import Notification
|
||||
from webpush.models import PushInformation
|
||||
|
||||
|
||||
class WebPushRecord(models.Model):
|
||||
|
||||
DEFAULT_STATUS = 'NEW'
|
||||
ERR_STATUS = 'ERR'
|
||||
OK_STATUS = 'SENT'
|
||||
STATUS = (
|
||||
(DEFAULT_STATUS, 'Not-sent notification'),
|
||||
(OK_STATUS, 'Sent notification'),
|
||||
(ERR_STATUS, 'Invalid notification'),
|
||||
)
|
||||
|
||||
subscription = models.ForeignKey(PushInformation, null=False)
|
||||
notification = models.ForeignKey(Notification, null=False)
|
||||
creation_date = models.DateTimeField(blank=True, auto_now_add=True)
|
||||
status = models.CharField(blank=True, max_length=4,
|
||||
choices=STATUS, default=DEFAULT_STATUS)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('subscription', 'notification')
|
||||
|
||||
@property
|
||||
def ttl(self):
|
||||
delta = self.notification.end_timestamp - self.notification.start_timestamp
|
||||
return int(delta.total_seconds())
|
||||
|
||||
@property
|
||||
def payload(self):
|
||||
'''
|
||||
For example, we could extend later this data to such JSON supported by Chrome for Android
|
||||
{
|
||||
"body": "Did you make a $1,000,000 purchase at Dr. Evil...",
|
||||
"icon": "images/ccard.png",
|
||||
"vibrate": [200, 100, 200, 100, 200, 100, 400],
|
||||
"tag": "request",
|
||||
"actions": [
|
||||
{ "action": "yes", "title": "Yes", "icon": "images/yes.png" },
|
||||
{ "action": "no", "title": "No", "icon": "images/no.png" }
|
||||
]
|
||||
}
|
||||
'''
|
||||
return {
|
||||
'head': self.notification.summary,
|
||||
'body': self.notification.body,
|
||||
'url': self.notification.url,
|
||||
}
|
||||
|
||||
def set_status_ok(self):
|
||||
self.status = self.OK_STATUS
|
||||
self.save()
|
||||
|
||||
def set_status_err(self):
|
||||
self.status = self.ERR_STATUS
|
||||
self.save()
|
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# combo-plugin-gnm - Combo WebPush App
|
||||
# Copyright (C) 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.conf import settings
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import is_naive, make_aware
|
||||
|
||||
from pywebpush import WebPushException
|
||||
from retrying import retry
|
||||
from webpush.utils import _send_notification
|
||||
|
||||
from .models import WebPushRecord
|
||||
|
||||
|
||||
def retry_if_webpush_exception(exception):
|
||||
'''Return True if we should retry'''
|
||||
return isinstance(exception, WebPushException)
|
||||
|
||||
|
||||
@retry(wait_exponential_multiplier=1000,
|
||||
wait_exponential_max=getattr(settings, 'WEBPUSH_BACKOFF_MAXMILLISECS', 10000),
|
||||
retry_on_exception=retry_if_webpush_exception)
|
||||
def send_web_push(notification, since=None):
|
||||
'''Get all the user subscriptions (every browsers with authoorized notifications)
|
||||
and send a push mesg to each one
|
||||
'''
|
||||
if is_naive(since):
|
||||
since = make_aware(since)
|
||||
user_subscription_list = notification.user.webpush_info.select_related("subscription")
|
||||
responses = []
|
||||
for sub_info in user_subscription_list:
|
||||
with transaction.atomic():
|
||||
web_push_record, created = WebPushRecord.objects.select_for_update().get_or_create(
|
||||
notification=notification,
|
||||
subscription=sub_info)
|
||||
|
||||
if web_push_record.status == WebPushRecord.DEFAULT_STATUS and \
|
||||
web_push_record.creation_date >= since:
|
||||
# check the user's subscription and send the request to the endpoint
|
||||
req = _send_notification(sub_info, web_push_record.payload, web_push_record.ttl)
|
||||
# requests.Response object
|
||||
if req.status_code <= 201:
|
||||
web_push_record.status = web_push_record.OK_STATUS
|
||||
web_push_record.save()
|
||||
responses += [req]
|
||||
else:
|
||||
web_push_record.status = web_push_record.ERR_STATUS
|
||||
web_push_record.save()
|
||||
|
||||
else:
|
||||
web_push_record.set_status_err()
|
||||
|
||||
return responses
|
|
@ -0,0 +1,238 @@
|
|||
// Based On https://raw.githubusercontent.com/safwanrahman/django-webpush/aed8d5213d76857bfb3a020c1c6b8af18cad0f12/webpush/static/webpush/webpush.js
|
||||
|
||||
// combo-plugin-gnm - Combo GNM plugin
|
||||
// Copyright (C) 2018 Entr'ouvert
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
var subBtn,
|
||||
messageBox,
|
||||
registration;
|
||||
|
||||
var activateTextMessage = 'Activez les notifications';
|
||||
var stopTextMessage = 'Stoppez les notifications';
|
||||
var incompatibleMessage = 'Ce navigateur n'est pas compatible avec les notifications push.';
|
||||
var browserShortName = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
|
||||
|
||||
|
||||
$(window).load(function () {
|
||||
subBtn = $('#webpush-subscribe-checkbox');
|
||||
messageBox = $('#webpush-subscribe-message');
|
||||
|
||||
function set_btn_activate() {
|
||||
subBtn.attr('disabled', false);
|
||||
subBtn.attr('checked', false);
|
||||
subBtn.removeClass("checked");
|
||||
messageBox.text(activateTextMessage);
|
||||
}
|
||||
|
||||
function set_btn_cancel() {
|
||||
subBtn.attr('disabled', false);
|
||||
subBtn.attr('checked', true);
|
||||
subBtn.addClass("checked");
|
||||
messageBox.text(stopTextMessage);
|
||||
}
|
||||
|
||||
var user_subscription_browser_list = subBtn.data('userSubscriptionBrowserList').split("=#=#");
|
||||
if (user_subscription_browser_list.indexOf(browserShortName) >= 0) {
|
||||
set_btn_cancel();
|
||||
} else {
|
||||
set_btn_activate();
|
||||
}
|
||||
|
||||
// show warning to the message box and disabled the input
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
messageBox.text(incompatibleMessage);
|
||||
subBtn.attr('checked', false);
|
||||
subBtn.attr('disabled', true);
|
||||
return;
|
||||
}
|
||||
|
||||
subBtn.click(
|
||||
function (evt) {
|
||||
subBtn.attr('disabled', true);
|
||||
// if the Browser Supports Service Worker
|
||||
// registered the worker and the click event hanlder
|
||||
if ('serviceWorker' in navigator) {
|
||||
var serviceWorker = document.getElementById('service-worker-js').src;
|
||||
navigator.serviceWorker.register(serviceWorker)
|
||||
.then(
|
||||
function (reg) {
|
||||
messageBox.text('Connexion au serveur en cours...');
|
||||
registration = reg;
|
||||
initialiseState(reg);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Once the service worker is registered set the initial state
|
||||
function initialiseState(reg) {
|
||||
// Check if PushManager, Notification is supported in the browser
|
||||
if (!('PushManager' in window) || !(reg.showNotification)) {
|
||||
messageBox.text(incompatibleMessage);
|
||||
subBtn.attr('checked', false);
|
||||
subBtn.attr('disabled', true);
|
||||
return;
|
||||
}
|
||||
// Check the current Notification permission.
|
||||
// If its denied, it's a permanent block until the
|
||||
// user changes the permission
|
||||
if (Notification.permission === 'denied') {
|
||||
// Show a message and activate the button
|
||||
set_btn_activate();
|
||||
return;
|
||||
}
|
||||
if (subBtn.filter(':checked')
|
||||
.length > 0) {
|
||||
return subscribe(reg);
|
||||
} else {
|
||||
return unsubscribe(reg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function subscribe(reg) {
|
||||
// Get the Subscription or register one
|
||||
getSubscription(reg)
|
||||
.then(
|
||||
function (subscription) {
|
||||
postSubscribeObj('subscribe', subscription);
|
||||
}
|
||||
)
|
||||
.catch(
|
||||
function (error) {
|
||||
messageBox.text('Impossible de communiquer avec le serveur, veuillez réessayer dans quelques minutes (Debug = '+ error +')');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function urlB64ToUint8Array(base64String) {
|
||||
|
||||
var b64padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
var base64 = (base64String + b64padding)
|
||||
.replace(/\-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
var rawData = window.atob(base64);
|
||||
var outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (var i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
function getSubscription(reg) {
|
||||
return reg.pushManager.getSubscription()
|
||||
.then(
|
||||
function (subscription) {
|
||||
var metaObj, applicationServerKey, options;
|
||||
// Check if Subscription is available
|
||||
if (subscription) {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
metaObj = document.querySelector('meta[name="django-webpush-vapid-key"]');
|
||||
applicationServerKey = metaObj.content;
|
||||
options = {
|
||||
userVisibleOnly: true
|
||||
};
|
||||
if (applicationServerKey) {
|
||||
options.applicationServerKey = urlB64ToUint8Array(applicationServerKey)
|
||||
}
|
||||
// If not, register one
|
||||
return registration.pushManager.subscribe(options)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function unsubscribe() {
|
||||
// Get the Subscription to unregister
|
||||
registration.pushManager.getSubscription()
|
||||
.then(
|
||||
function (subscription) {
|
||||
|
||||
// Check we have a subscription to unsubscribe
|
||||
if (!subscription) {
|
||||
// No subscription object, so set the state
|
||||
// to allow the user to subscribe to push
|
||||
set_btn_activate();
|
||||
return;
|
||||
}
|
||||
postSubscribeObj('unsubscribe', subscription);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function postSubscribeObj(statusType, subscription) {
|
||||
// Send the information to the server with fetch API.
|
||||
// the type of the request, the name of the user subscribing,
|
||||
// and the push subscription endpoint + key the server needs
|
||||
// to send push messages
|
||||
|
||||
// Each subscription is different for each of these navigator userAgent short name
|
||||
var data = {
|
||||
status_type: statusType,
|
||||
subscription: subscription.toJSON(),
|
||||
browser: browserShortName
|
||||
// group: subBtn.dataset.group
|
||||
};
|
||||
|
||||
fetch(subBtn.data('url'), {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(
|
||||
function (response) {
|
||||
// Check the information is saved successfully into server
|
||||
if ((response.status == 201) && (statusType == 'subscribe')) {
|
||||
// Show unsubscribe button instead
|
||||
set_btn_cancel();
|
||||
}
|
||||
|
||||
// Check if the information is deleted from server
|
||||
if ((response.status == 202) && (statusType == 'unsubscribe')) {
|
||||
// Get the Subscription
|
||||
getSubscription(registration)
|
||||
.then(
|
||||
function (subscription) {
|
||||
// Remove the subscription
|
||||
subscription.unsubscribe()
|
||||
.then(
|
||||
function () {
|
||||
set_btn_activate();
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
.catch(
|
||||
function (error) {
|
||||
subBtn.attr('disabled', false);
|
||||
subBtn.attr('checked', false);
|
||||
subBtn.removeClass("checked");
|
||||
messageBox.text('Erreur lors de la requête, veuillez réessayer dans quelques minutes (Debug = '+ error +')');
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
{% load i18n static gnm gnm_notifications %}
|
||||
|
||||
|
||||
<div class="notification-dialog">
|
||||
{% if request.user.is_authenticated or group %}
|
||||
{% webpush_scripts %}
|
||||
<label class="checkbox">
|
||||
<input id="webpush-subscribe-checkbox" class="checkbox_input" type="checkbox"
|
||||
{% if group %}data-group="{{ group }}"{% endif %} data-url="{{ url }}"
|
||||
data-user-subscription-browser-list="{{ user_subscription_browser_list|join:'=#=#'}}">
|
||||
<p id="webpush-subscribe-message" class="checkbox_text"></p>
|
||||
</label>
|
||||
{% else %}
|
||||
<label class="checkbox">{% trans "Login to receive push notifications" %}</label>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
{% load static %}
|
||||
|
||||
<script id="service-worker-js" type="text/javascript" src="{{site_base}}{% static 'webpush/webpush_serviceworker.js' %}?{{statics_hash}}"></script>
|
||||
<script type="text/javascript" src="{{site_base}}{% static 'combo_plugin_gnm/webpush.js' %}?{{statics_hash}}"></script>
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# combo-plugin-gnm - Combo GNM plugin
|
||||
# Copyright (C) 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 django.core.urlresolvers import reverse
|
||||
|
||||
from webpush.models import PushInformation
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('webpush_checkbox.html', takes_context=True)
|
||||
def webpush_checkbox(context):
|
||||
group = context.get('webpush', {}).get('group')
|
||||
url = reverse('save_webpush_info')
|
||||
request = context['request']
|
||||
user_subscription_browser_list = [info.subscription.browser for info in PushInformation.objects.filter(user=request.user)]
|
||||
print user_subscription_browser_list
|
||||
return {
|
||||
'group': group,
|
||||
'url': url,
|
||||
'request': request,
|
||||
'user_subscription_browser_list': user_subscription_browser_list
|
||||
}
|
||||
|
||||
|
||||
@register.inclusion_tag('webpush_scripts.html', takes_context=True)
|
||||
def webpush_scripts(context):
|
||||
return
|
||||
|
||||
|
||||
@register.assignment_tag(takes_context=True)
|
||||
def get_webpush_vars(context):
|
||||
vapid_public_key = getattr(settings, 'WEBPUSH_SETTINGS', {}).get('VAPID_PUBLIC_KEY', '')
|
||||
return {'vapid_public_key': vapid_public_key}
|
|
@ -15,10 +15,12 @@
|
|||
# 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.conf.urls import url
|
||||
from django.conf.urls import url, include
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^gnm/plusone/$', views.plusone, name='gnm-plus-one'),
|
||||
url(r'^gnm/webpush/backend/', include('webpush.urls')),
|
||||
]
|
||||
|
|
|
@ -22,6 +22,7 @@ from django.template import RequestContext
|
|||
|
||||
from combo.utils import requests, get_templated_url
|
||||
|
||||
|
||||
def plusone(request, *args, **kwargs):
|
||||
# add reference to a jsondatastore with slug "plus1"
|
||||
reference = request.GET.get('ref')
|
||||
|
|
|
@ -279,3 +279,6 @@ JSON_CELL_TYPES = {
|
|||
|
||||
import memcache
|
||||
memcache.SERVER_MAX_VALUE_LENGTH = 10 * 1024 * 1024
|
||||
|
||||
# Exponential backoff maximum milliseconds spent retrying
|
||||
WEBPUSH_BACKOFF_MAXMILLISECS = 10000
|
||||
|
|
5
setup.py
5
setup.py
|
@ -97,6 +97,11 @@ setup(
|
|||
install_requires=[
|
||||
'django>=1.8, <1.12',
|
||||
'python-dateutil',
|
||||
'retrying',
|
||||
'django-webpush>=0.2.2.3'
|
||||
],
|
||||
dependency_links=[
|
||||
'https://github.com/elishowk/django-webpush/tarball/master#egg=django-webpush-0.2.2.3',
|
||||
],
|
||||
zip_safe=False,
|
||||
entry_points={
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import pytest
|
||||
|
||||
import django_webtest
|
||||
|
||||
@pytest.fixture
|
||||
def app(request):
|
||||
wtm = django_webtest.WebTestMixin()
|
||||
wtm._patch_settings()
|
||||
request.addfinalizer(wtm._unpatch_settings)
|
||||
return django_webtest.DjangoTestApp()
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
USE_TZ = True
|
|
@ -0,0 +1,94 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# combo-plugin-gnm - Combo WebPush App
|
||||
# Copyright (C) 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/>.
|
||||
|
||||
import os
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
import pytest
|
||||
from mock import patch
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from webpush.models import SubscriptionInfo, PushInformation
|
||||
|
||||
from combo.apps.notifications.models import Notification
|
||||
import combo_plugin_gnm
|
||||
from combo_plugin_gnm.models import WebPushRecord
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user1():
|
||||
user1 = User.objects.create_user('user1', email='user1@example.net', password='user1')
|
||||
user1.save()
|
||||
return user1
|
||||
|
||||
|
||||
def _get_pubkey_str(priv_key):
|
||||
'''From pywebpush tests'''
|
||||
return base64.urlsafe_b64encode(
|
||||
priv_key.public_key().public_numbers().encode_point()
|
||||
).strip(b'=')
|
||||
|
||||
|
||||
def _gen_subscription_info(recv_key=None,
|
||||
endpoint="https://example.com/"):
|
||||
'''From pywebpush tests'''
|
||||
if not recv_key:
|
||||
recv_key = ec.generate_private_key(ec.SECP256R1, default_backend())
|
||||
return {
|
||||
"endpoint": endpoint,
|
||||
"keys": {
|
||||
'auth': base64.urlsafe_b64encode(os.urandom(16)).strip(b'='),
|
||||
'p256dh': _get_pubkey_str(recv_key),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@patch('pywebpush.WebPusher.send')
|
||||
def test_command_send_webpush(mock_send, user1):
|
||||
mock_send.return_value.status_code = 201
|
||||
recv_key = ec.generate_private_key(ec.SECP256R1, default_backend())
|
||||
subscription_keys_dict = _gen_subscription_info(recv_key)
|
||||
subscription_record = SubscriptionInfo.objects.create(
|
||||
browser='test browser',
|
||||
endpoint=subscription_keys_dict['endpoint'],
|
||||
auth=subscription_keys_dict['keys']['auth'],
|
||||
p256dh=subscription_keys_dict['keys']['p256dh'],
|
||||
)
|
||||
push_info = PushInformation.objects.create(
|
||||
user=user1,
|
||||
subscription=subscription_record
|
||||
)
|
||||
notification = Notification.notify(user1, u'tèst headér', body=u'test uniçode bodéï')
|
||||
call_command('send_webpush', '--verbosity=1')
|
||||
assert WebPushRecord.objects.filter(status=WebPushRecord.ERR_STATUS).count() == 0
|
||||
assert WebPushRecord.objects.filter(status=WebPushRecord.DEFAULT_STATUS).count() == 0
|
||||
ok_push = WebPushRecord.objects.filter(status=WebPushRecord.OK_STATUS)
|
||||
assert ok_push.count() == 1
|
||||
ok_push = ok_push.first()
|
||||
assert ok_push.subscription == push_info
|
||||
assert ok_push.notification == notification
|
||||
assert ok_push.creation_date < make_aware(datetime.now())
|
Loading…
Reference in New Issue