From 2aafd9097b8134d615abd107a4f51d1379670874 Mon Sep 17 00:00:00 2001 From: Serghei Mihai Date: Tue, 14 Nov 2017 18:48:44 +0100 Subject: [PATCH] lingo: notify new remote invoices (#13122) --- combo/apps/lingo/models.py | 41 +++++++++++++++++++++++- combo/settings.py | 3 ++ tests/test_notification.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/combo/apps/lingo/models.py b/combo/apps/lingo/models.py index 3c9efa6..54c94e4 100644 --- a/combo/apps/lingo/models.py +++ b/combo/apps/lingo/models.py @@ -32,14 +32,17 @@ from django.conf import settings from django.db import models from django.forms import models as model_forms, Select from django.utils.translation import ugettext_lazy as _ -from django.utils import timezone +from django.utils import timezone, dateparse from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.utils.http import urlencode +from django.contrib.auth.models import User + from combo.data.fields import RichTextField from combo.data.models import CellBase from combo.data.library import register_cell_class from combo.utils import NothingInCacheException, aes_hex_encrypt, requests +from combo.apps.notifications.models import Notification EXPIRED = 9999 @@ -203,6 +206,42 @@ class Regie(models.Model): extra_fee=True, user_cancellable=False).save() + def notify_new_remote_invoices(self): + if not self.is_remote(): + return + + logger = logging.getLogger(__name__) + url = self.webservice_url + '/users/with-pending-invoices/' + response = requests.get(url, remote_service='auto', cache_duration=0, + log_errors=False) + if not response.ok: + return + + data = response.json()['data'] + if not data: + return + for uuid, items in data.iteritems(): + try: + user = User.objects.get(username=uuid) + except User.DoesNotExist: + logger.warning('Invoices available for unknown user: %s', uuid) + continue + for invoice in items['invoices']: + invoice_id = 'invoice-%s-%s' % (self.slug, invoice['id']) + invoice_creation_date = timezone.make_aware(dateparse.parse_datetime(invoice['created'])) + invoice_pay_limit_date = timezone.make_aware(dateparse.parse_datetime(invoice['pay_limit_date'])) + notification_end_timestamp = invoice_creation_date + timezone.timedelta(days=settings.LINGO_NEW_INVOICES_NOTIFICATION_DELAY) + if invoice_pay_limit_date < notification_end_timestamp: + notification_end_timestamp = invoice_pay_limit_date + notification_id, created = Notification.notify(user, _('Invoice %s to pay') % invoice['label'], id=invoice_id, + end_timestamp=notification_end_timestamp) + if not created: + notification = Notification.objects.filter_by_id(invoice_id).filter(user=user).last() + if notification.end_timestamp < timezone.now(): + remind_id = 'remind-%s' % invoice_id + Notification.notify(user, _('Reminder: invoice %s to pay') % invoice['label'], + id=remind_id, end_timestamp=notification_end_timestamp) + class BasketItem(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True) diff --git a/combo/settings.py b/combo/settings.py index a14a94b..8329c13 100644 --- a/combo/settings.py +++ b/combo/settings.py @@ -279,6 +279,9 @@ COMBO_MAP_TILE_URLTEMPLATE = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png # default combo map attribution COMBO_MAP_ATTRIBUTION = 'Map data © OpenStreetMap contributors, CC-BY-SA' +# default delay for invoice payment notifications in days +LINGO_NEW_INVOICES_NOTIFICATION_DELAY = 10 + # timeout used in python-requests call, in seconds # we use 28s by default: timeout just before web server, which is usually 30s REQUESTS_TIMEOUT = 28 diff --git a/tests/test_notification.py b/tests/test_notification.py index 6f58b7d..8e6299b 100644 --- a/tests/test_notification.py +++ b/tests/test_notification.py @@ -1,9 +1,12 @@ import json +import mock import pytest +from decimal import Decimal from django.contrib.auth.models import User from django.test.client import RequestFactory +from django.test import override_settings from django.utils.timezone import timedelta, now from django.core.urlresolvers import reverse @@ -11,6 +14,7 @@ from django.test import Client from combo.data.models import Page from combo.apps.notifications.models import Notification, NotificationsCell +from combo.apps.lingo.models import Regie pytestmark = pytest.mark.django_db @@ -38,6 +42,19 @@ def login(username='admin', password='admin'): resp = client.post('/login/', {'username': username, 'password': password}) assert resp.status_code == 302 +@pytest.fixture +def regie(): + try: + regie = Regie.objects.get(slug='remote') + except Regie.DoesNotExist: + regie = Regie() + regie.label = 'Remote' + regie.slug = 'remote' + regie.description = 'remote' + regie.payment_min_amount = Decimal(2.0) + regie.service = 'dummy' + regie.save() + return regie def test_notification_api(user, user2): id_notifoo, created = Notification.notify(user, 'notifoo') @@ -236,3 +253,63 @@ def test_notification_ws_check_urls(): kwargs={'notification_id': 'noti1'}) == '/api/notification/ack/noti1/' assert reverse('api-notification-forget', kwargs={'notification_id': 'noti1'}) == '/api/notification/forget/noti1/' + +@mock.patch('combo.apps.lingo.models.requests.get') +def test_notify_remote_items(mock_get, app, user, user2, regie, caplog): + datetime_format = '%Y-%m-%dT%H:%M:%S' + invoice_now = now() + creation_date = (invoice_now - timedelta(days=1)).strftime(datetime_format) + pay_limit_date = (invoice_now + timedelta(days=30)).strftime(datetime_format) + FAKE_PENDING_INVOICES = { + "data" : {"admin": {"invoices": [{'id': '01', 'label': '010101', + 'created': creation_date, 'pay_limit_date': pay_limit_date}]}, + 'admin2': {'invoices': [{'id': '02', 'label': '020202', + 'created': creation_date, 'pay_limit_date': pay_limit_date}]}, + 'foo': {'invoices': [{'id': 'O3', 'label': '030303', + 'created': creation_date, 'pay_limite_date': pay_limit_date}]} + } + } + mock_response = mock.Mock(status_code=200, content=json.dumps(FAKE_PENDING_INVOICES)) + mock_response.json.return_value = FAKE_PENDING_INVOICES + mock_get.return_value = mock_response + regie.notify_new_remote_invoices() + assert mock_get.call_count == 0 + regie.webservice_url = 'http://example.org/regie' # is_remote + regie.save() + with override_settings(LINGO_NEW_INVOICES_NOTIFICATION_DELAY=0): + regie.notify_new_remote_invoices() + + assert Notification.objects.count() == 2 + + # create remind notifications + regie.notify_new_remote_invoices() + assert Notification.objects.count() == 4 + + assert len(caplog.records) == 2 + + +@mock.patch('combo.apps.lingo.models.requests.get') +def test_notify_remote_items_expiring_shortly(mock_get, app, user, user2, regie): + datetime_format = '%Y-%m-%dT%H:%M:%S' + invoice_now = now() + creation_date = (invoice_now - timedelta(1)).strftime(datetime_format) + pay_limit_date = (invoice_now + timedelta(days=5)).strftime(datetime_format) + SHORT_PENDING_INVOICES = { + "data" : {"admin": {"invoices": [{'id': '03', 'label': '030303', + 'created': creation_date, + 'pay_limit_date': pay_limit_date}]}, + 'admin2': {'invoices': [{'id': '04', 'label': '040404', + 'created': creation_date, + 'pay_limit_date': pay_limit_date}]}, + } + } + mock_response = mock.Mock(status_code=200, content=json.dumps(SHORT_PENDING_INVOICES)) + mock_response.json.return_value = SHORT_PENDING_INVOICES + mock_get.return_value = mock_response + regie.webservice_url = 'http://example.org/regie' + regie.save() + regie.notify_new_remote_invoices() + + assert Notification.objects.count() == 2 + for notification in Notification.objects.all(): + assert notification.end_timestamp.strftime(datetime_format) == pay_limit_date -- 2.16.2