Projet

Général

Profil

0001-api-add-sms-count-statistics-53856.patch

Valentin Deniaud, 31 mai 2021 14:27

Télécharger (10,3 ko)

Voir les différences:

Subject: [PATCH] api: add sms count statistics (#53856)

 passerelle/api/urls.py   |  3 ++-
 passerelle/api/views.py  | 13 +++++++++++-
 passerelle/plugins.py    |  9 +++++++-
 passerelle/sms/models.py | 35 +++++++++++++++++++++++++++++++
 passerelle/sms/views.py  | 45 +++++++++++++++++++++++++++++++++++++++-
 passerelle/urls_utils.py | 12 +++++++++++
 tests/test_sms.py        | 40 +++++++++++++++++++++++++++++++++++
 7 files changed, 153 insertions(+), 4 deletions(-)
passerelle/api/urls.py
16 16

  
17 17
from django.conf.urls import url
18 18

  
19
from .views import JobDetailView
19
from .views import JobDetailView, StatisticsListView
20 20

  
21 21
urlpatterns = [
22 22
    url(r'jobs/(?P<pk>[\w,-]+)/$', JobDetailView.as_view(), name='api-job'),
23
    url(r'statistics/$', StatisticsListView.as_view(), name='api-statistics-list'),
23 24
]
passerelle/api/views.py
16 16

  
17 17
from django.core.exceptions import PermissionDenied
18 18
from django.http import Http404, JsonResponse
19
from django.views.generic import DetailView
19
from django.views.generic import DetailView, View
20 20

  
21 21
from passerelle.base.models import Job
22 22
from passerelle.utils import is_authorized
23
from passerelle.views import get_all_apps
23 24

  
24 25

  
25 26
class JobDetailView(DetailView):
......
45 46
            'done_timestamp': job.done_timestamp,
46 47
        }
47 48
        return JsonResponse({'err': 0, 'data': data})
49

  
50

  
51
class StatisticsListView(View):
52
    def get(self, request, *args, **kwargs):
53
        sources = []
54
        for app in get_all_apps():
55
            for connector in app.objects.all():
56
                if hasattr(connector, 'get_statistics_entries'):
57
                    sources.extend(connector.get_statistics_entries(request))
58
        return JsonResponse({'data': sources})
passerelle/plugins.py
17 17
from django.apps import apps
18 18
from django.conf.urls import include, url
19 19

  
20
from .urls_utils import app_enabled, decorated_includes, manager_required, required
20
from .urls_utils import app_enabled, decorated_includes, manager_required, required, trust_required
21 21

  
22 22

  
23 23
def register_apps_urls(urlpatterns):
......
54 54
                    urls = required(app_enabled(app.label), urls)
55 55
                    urls = required(manager_required, urls)
56 56
                    after_urls.append(url(url_prefix, include(urls), kwargs={'connector': connector_slug}))
57
            if hasattr(obj, 'get_statistics_urls'):
58
                url_prefix = '^api/%s/' % connector_slug
59
                urls = obj.get_statistics_urls()
60
                if urls:
61
                    urls = required(app_enabled(app.label), urls)
62
                    urls = required(trust_required, urls)
63
                    after_urls.append(url(url_prefix, include(urls), kwargs={'connector': connector_slug}))
57 64

  
58 65
    return before_urls + urlpatterns + after_urls
passerelle/sms/models.py
16 16
import logging
17 17
import re
18 18

  
19
from django.conf.urls import url
19 20
from django.contrib.postgres.fields import ArrayField
20 21
from django.db import models
22
from django.urls import reverse
21 23
from django.utils import six
22 24
from django.utils.module_loading import import_string
23 25
from django.utils.translation import ugettext_lazy as _
......
99 101
    def get_management_urls(cls):
100 102
        return import_string('passerelle.sms.urls.management_urlpatterns')
101 103

  
104
    @classmethod
105
    def get_statistics_urls(cls):
106
        from .views import SmsStatisticsView
107

  
108
        statistics_urlpatterns = [
109
            url(
110
                r'^(?P<slug>[\w,-]+)/sms-count/$',
111
                SmsStatisticsView.as_view(),
112
                name='api-statistics-sms-%s' % cls.get_connector_slug(),
113
            ),
114
        ]
115
        return statistics_urlpatterns
116

  
102 117
    def _get_authorized_display(self):
103 118
        result = []
104 119
        for key, value in self.AUTHORIZED:
......
202 217
        self.send_msg(**kwargs)
203 218
        SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
204 219

  
220
    def get_statistics_entries(self, request):
221
        return [
222
            {
223
                'name': _('SMS Count (%s)') % self.slug,
224
                'url': request.build_absolute_uri(
225
                    reverse('api-statistics-sms-%s' % self.get_connector_slug(), kwargs={'slug': self.slug}),
226
                ),
227
                'id': 'sms_count_%s_%s' % (self.get_connector_slug(), self.slug),
228
                'filters': [
229
                    {
230
                        'id': 'time_interval',
231
                        'label': _('Interval'),
232
                        'options': [{'id': 'day', 'label': _('Day')}],
233
                        'required': True,
234
                        'default': 'day',
235
                    },
236
                ],
237
            }
238
        ]
239

  
205 240
    class Meta:
206 241
        abstract = True
207 242

  
passerelle/sms/views.py
1
import datetime
2

  
1 3
from django.apps import apps
2 4
from django.contrib import messages
5
from django.db.models import Count
6
from django.db.models.functions import TruncDay
7
from django.http import JsonResponse
8
from django.utils.timezone import make_aware
3 9
from django.utils.translation import ugettext_lazy as _
4
from django.views.generic import FormView
10
from django.views.generic import FormView, View
5 11

  
6 12
from passerelle.utils.jsonresponse import APIError
7 13
from passerelle.views import GenericConnectorMixin
8 14

  
9 15
from .forms import SmsTestSendForm
16
from .models import SMSLog
10 17

  
11 18

  
12 19
class SmsTestSendView(GenericConnectorMixin, FormView):
......
36 43
        else:
37 44
            messages.success(self.request, _('An SMS was just sent'))
38 45
        return super(SmsTestSendView, self).form_valid(form)
46

  
47

  
48
class SmsStatisticsView(View):
49
    def get(self, request, *args, **kwargs):
50
        if 'time_interval' in request.GET and request.GET['time_interval'] != 'day':
51
            return JsonResponse({'err': 1, 'err_desc': 'unsupported time interval'})
52

  
53
        logs = SMSLog.objects.filter(appname=kwargs['connector'], slug=kwargs['slug'])
54
        if 'start' in request.GET:
55
            start = make_aware(datetime.datetime.strptime(request.GET['start'], '%Y-%m-%d'))
56
            logs = logs.filter(timestamp__gte=start)
57
        if 'end' in request.GET:
58
            end = make_aware(datetime.datetime.strptime(request.GET['end'], '%Y-%m-%d'))
59
            logs = logs.filter(timestamp__lte=end)
60

  
61
        logs = logs.annotate(day=TruncDay('timestamp'))
62
        logs = logs.values('day').annotate(total=Count('id')).order_by('day')
63

  
64
        x_labels, data = [], []
65
        for log in logs:
66
            x_labels.append(log['day'].strftime('%Y-%m-%d'))
67
            data.append(log['total'])
68

  
69
        return JsonResponse(
70
            {
71
                'data': {
72
                    'x_labels': x_labels,
73
                    'series': [
74
                        {
75
                            'label': _('SMS Count'),
76
                            'data': data,
77
                        }
78
                    ],
79
                }
80
            }
81
        )
passerelle/urls_utils.py
15 15
from django.http import Http404
16 16
from django.views.debug import technical_404_response
17 17

  
18
from passerelle.utils import is_trusted
19

  
18 20

  
19 21
class DecoratedURLPattern(URLPattern):
20 22
    def resolve(self, *args, **kwargs):
......
148 150
    if function:
149 151
        return actual_decorator(function)
150 152
    return actual_decorator
153

  
154

  
155
def trust_required(func):
156
    @wraps(func)
157
    def f(request, *args, **kwargs):
158
        if not (request.user.is_superuser or is_trusted(request)):
159
            raise PermissionDenied()
160
        return func(request, *args, **kwargs)
161

  
162
    return f
tests/test_sms.py
566 566
        ResourceLog.objects.filter(levelno=30)[0].extra['exception']
567 567
        == 'no phone number was authorized: 0033188888888'
568 568
    )
569

  
570

  
571
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
572
def test_api_statistics(app, freezer, connector, admin_user):
573
    resp = app.get('/api/statistics/')
574
    url = [x for x in resp.json['data'] if x['id'] == 'sms_count_ovh_ovhsmsgateway'][0]['url']
575

  
576
    assert app.get(url, status=403)
577

  
578
    login(app)
579
    resp = app.get(url)
580
    assert len(resp.json['data']['series'][0]['data']) == 0
581

  
582
    freezer.move_to('2021-01-01 12:00')
583
    for _ in range(5):
584
        SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
585

  
586
    freezer.move_to('2021-02-03 13:00')
587
    for _ in range(3):
588
        SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
589

  
590
    freezer.move_to('2021-02-06 13:00')
591
    SMSLog.objects.create(appname='ovh', slug='ovhsmsgateway')
592
    SMSLog.objects.create(appname='ovh', slug='other')
593

  
594
    resp = app.get(url + '?time_interval=day')
595
    assert resp.json['data'] == {
596
        'x_labels': ['2021-01-01', '2021-02-03', '2021-02-06'],
597
        'series': [{'label': 'SMS Count', 'data': [5, 3, 1]}],
598
    }
599

  
600
    resp = app.get(url + '?start=2021-02-04&end=2021-02-07')
601
    assert resp.json['data'] == {
602
        'x_labels': ['2021-02-06'],
603
        'series': [{'label': 'SMS Count', 'data': [1]}],
604
    }
605

  
606
    # invalid time_interval
607
    resp = app.get(url + '?time_interval=month')
608
    assert resp.json['err'] == 1
569
-