0001-api-add-sms-count-statistics-53856.patch
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 |
- |