Projet

Général

Profil

0001-lingo-notify-new-remote-invoices-13122.patch

Voir les différences:

Subject: [PATCH] lingo: notify new remote invoices (#13122)

 .../commands/notify_new_remote_invoices.py         | 34 ++++++++++
 combo/apps/lingo/models.py                         | 41 +++++++++++-
 combo/settings.py                                  |  3 +
 tests/test_notification.py                         | 77 ++++++++++++++++++++++
 4 files changed, 154 insertions(+), 1 deletion(-)
 create mode 100644 combo/apps/lingo/management/commands/notify_new_remote_invoices.py
combo/apps/lingo/management/commands/notify_new_remote_invoices.py
1
# -*- coding: utf-8 -*-
2
#
3
# lingo - basket and payment system
4
# Copyright (C) 2018  Entr'ouvert
5
#
6
# This program is free software: you can redistribute it and/or modify it
7
# under the terms of the GNU Affero General Public License as published
8
# by the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU Affero General Public License for more details.
15
#
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18

  
19
import logging
20

  
21
from django.core.management.base import BaseCommand
22

  
23
from combo.apps.lingo.models import Regie
24

  
25

  
26
class Command(BaseCommand):
27

  
28
    def handle(self, *args, **kwargs):
29
        logger = logging.getLogger(__name__)
30
        for regie in Regie.objects.exclude(webservice_url=''):
31
            try:
32
                regie.notify_new_remote_invoices()
33
            except Exception, e:
34
                logger.exception('error while notifying new remote invoices: %s', e)
combo/apps/lingo/models.py
32 32
from django.db import models
33 33
from django.forms import models as model_forms, Select
34 34
from django.utils.translation import ugettext_lazy as _
35
from django.utils import timezone
35
from django.utils import timezone, dateparse
36 36
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
37 37
from django.utils.http import urlencode
38 38

  
39
from django.contrib.auth.models import User
40

  
39 41
from combo.data.fields import RichTextField
40 42
from combo.data.models import CellBase
41 43
from combo.data.library import register_cell_class
42 44
from combo.utils import NothingInCacheException, aes_hex_encrypt, requests
45
from combo.apps.notifications.models import Notification
43 46

  
44 47
EXPIRED = 9999
45 48

  
......
203 206
                    extra_fee=True,
204 207
                    user_cancellable=False).save()
205 208

  
209
    def notify_new_remote_invoices(self):
210
        if not self.is_remote():
211
            return
212

  
213
        logger = logging.getLogger(__name__)
214
        url = self.webservice_url + '/users/with-pending-invoices/'
215
        response = requests.get(url, remote_service='auto', cache_duration=0,
216
                                log_errors=False)
217
        if not response.ok:
218
            return
219

  
220
        data = response.json()['data']
221
        if not data:
222
            return
223
        for uuid, items in data.iteritems():
224
            try:
225
                user = User.objects.get(username=uuid)
226
            except User.DoesNotExist:
227
                logger.warning('Invoices available for unknown user: %s', uuid)
228
                continue
229
            for invoice in items['invoices']:
230
                invoice_id = 'invoice-%s-%s' % (self.slug, invoice['id'])
231
                invoice_creation_date = timezone.make_aware(dateparse.parse_datetime(invoice['created']))
232
                invoice_pay_limit_date = timezone.make_aware(dateparse.parse_datetime(invoice['pay_limit_date']))
233
                notification_end_timestamp = invoice_creation_date + timezone.timedelta(days=settings.LINGO_NEW_INVOICES_NOTIFICATION_DELAY)
234
                if invoice_pay_limit_date < notification_end_timestamp:
235
                    notification_end_timestamp = invoice_pay_limit_date
236
                notification_id, created = Notification.notify(user, _('Invoice %s to pay') % invoice['label'], id=invoice_id,
237
                                                               end_timestamp=notification_end_timestamp)
238
                if not created:
239
                    notification = Notification.objects.filter_by_id(invoice_id).filter(user=user).last()
240
                    if notification.end_timestamp < timezone.now():
241
                        remind_id = 'remind-%s' % invoice_id
242
                        Notification.notify(user, _('Reminder: invoice %s to pay') % invoice['label'],
243
                                            id=remind_id, end_timestamp=notification_end_timestamp)
244

  
206 245

  
207 246
class BasketItem(models.Model):
208 247
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
combo/settings.py
279 279
# default combo map attribution
280 280
COMBO_MAP_ATTRIBUTION = 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
281 281

  
282
# default delay for invoice payment notifications in days
283
LINGO_NEW_INVOICES_NOTIFICATION_DELAY = 10
284

  
282 285
# timeout used in python-requests call, in seconds
283 286
# we use 28s by default: timeout just before web server, which is usually 30s
284 287
REQUESTS_TIMEOUT = 28
tests/test_notification.py
1 1
import json
2 2

  
3
import mock
3 4
import pytest
5
from decimal import Decimal
4 6

  
5 7
from django.contrib.auth.models import User
6 8
from django.test.client import RequestFactory
9
from django.test import override_settings
7 10
from django.utils.timezone import timedelta, now
8 11
from django.core.urlresolvers import reverse
9 12

  
......
11 14

  
12 15
from combo.data.models import Page
13 16
from combo.apps.notifications.models import Notification, NotificationsCell
17
from combo.apps.lingo.models import Regie
14 18

  
15 19
pytestmark = pytest.mark.django_db
16 20

  
......
38 42
    resp = client.post('/login/', {'username': username, 'password': password})
39 43
    assert resp.status_code == 302
40 44

  
45
@pytest.fixture
46
def regie():
47
    try:
48
        regie = Regie.objects.get(slug='remote')
49
    except Regie.DoesNotExist:
50
        regie = Regie()
51
        regie.label = 'Remote'
52
        regie.slug = 'remote'
53
        regie.description = 'remote'
54
        regie.payment_min_amount = Decimal(2.0)
55
        regie.service = 'dummy'
56
        regie.save()
57
    return regie
41 58

  
42 59
def test_notification_api(user, user2):
43 60
    id_notifoo, created = Notification.notify(user, 'notifoo')
......
236 253
                   kwargs={'notification_id': 'noti1'}) == '/api/notification/ack/noti1/'
237 254
    assert reverse('api-notification-forget',
238 255
                   kwargs={'notification_id': 'noti1'}) == '/api/notification/forget/noti1/'
256

  
257
@mock.patch('combo.apps.lingo.models.requests.get')
258
def test_notify_remote_items(mock_get, app, user, user2, regie, caplog):
259
    datetime_format = '%Y-%m-%dT%H:%M:%S'
260
    invoice_now = now()
261
    creation_date = (invoice_now - timedelta(days=1)).strftime(datetime_format)
262
    pay_limit_date = (invoice_now + timedelta(days=30)).strftime(datetime_format)
263
    FAKE_PENDING_INVOICES = {
264
        "data" :  {"admin": {"invoices": [{'id': '01', 'label': '010101',
265
                        'created': creation_date, 'pay_limit_date': pay_limit_date}]},
266
                   'admin2': {'invoices': [{'id': '02', 'label': '020202',
267
                        'created': creation_date, 'pay_limit_date': pay_limit_date}]},
268
                   'foo': {'invoices': [{'id': 'O3', 'label': '030303',
269
                        'created': creation_date, 'pay_limite_date': pay_limit_date}]}
270
        }
271
    }
272
    mock_response = mock.Mock(status_code=200, content=json.dumps(FAKE_PENDING_INVOICES))
273
    mock_response.json.return_value = FAKE_PENDING_INVOICES
274
    mock_get.return_value = mock_response
275
    regie.notify_new_remote_invoices()
276
    assert mock_get.call_count == 0
277
    regie.webservice_url = 'http://example.org/regie' # is_remote
278
    regie.save()
279
    with override_settings(LINGO_NEW_INVOICES_NOTIFICATION_DELAY=0):
280
        regie.notify_new_remote_invoices()
281

  
282
        assert Notification.objects.count() == 2
283

  
284
        # create remind notifications
285
        regie.notify_new_remote_invoices()
286
        assert Notification.objects.count() == 4
287

  
288
    assert len(caplog.records) == 2
289

  
290

  
291
@mock.patch('combo.apps.lingo.models.requests.get')
292
def test_notify_remote_items_expiring_shortly(mock_get, app, user, user2, regie):
293
    datetime_format = '%Y-%m-%dT%H:%M:%S'
294
    invoice_now = now()
295
    creation_date = (invoice_now - timedelta(1)).strftime(datetime_format)
296
    pay_limit_date = (invoice_now + timedelta(days=5)).strftime(datetime_format)
297
    SHORT_PENDING_INVOICES = {
298
        "data" :  {"admin": {"invoices": [{'id': '03', 'label': '030303',
299
                        'created': creation_date,
300
                        'pay_limit_date': pay_limit_date}]},
301
                   'admin2': {'invoices': [{'id': '04', 'label': '040404',
302
                        'created': creation_date,
303
                        'pay_limit_date': pay_limit_date}]},
304
        }
305
    }
306
    mock_response = mock.Mock(status_code=200, content=json.dumps(SHORT_PENDING_INVOICES))
307
    mock_response.json.return_value = SHORT_PENDING_INVOICES
308
    mock_get.return_value = mock_response
309
    regie.webservice_url = 'http://example.org/regie'
310
    regie.save()
311
    regie.notify_new_remote_invoices()
312

  
313
    assert Notification.objects.count() == 2
314
    for notification in Notification.objects.all():
315
        assert notification.end_timestamp.strftime(datetime_format) == pay_limit_date
239
-