0001-ovh-send-email-credit-alerts-42921.patch
passerelle/apps/ovh/migrations/0011_auto_20201008_1731.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-10-08 15:31 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
import django.contrib.postgres.fields |
|
6 |
from django.db import migrations, models |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('ovh', '0010_auto_20201008_1126'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.AddField( |
|
17 |
model_name='ovhsmsgateway', |
|
18 |
name='alert_emails', |
|
19 |
field=django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(blank=True, max_length=254), blank=True, null=True, size=None, verbose_name='Email addresses to send credit alerts to'), |
|
20 |
), |
|
21 |
migrations.AddField( |
|
22 |
model_name='ovhsmsgateway', |
|
23 |
name='credit_alert_timestamp', |
|
24 |
field=models.DateTimeField(null=True), |
|
25 |
), |
|
26 |
migrations.AlterField( |
|
27 |
model_name='ovhsmsgateway', |
|
28 |
name='credit_threshold_alert', |
|
29 |
field=models.PositiveIntegerField(default=500, verbose_name='Credit alert threshold'), |
|
30 |
), |
|
31 |
] |
passerelle/apps/ovh/models.py | ||
---|---|---|
2 | 2 |
import json |
3 | 3 |
import requests |
4 | 4 |
import time |
5 |
from datetime import timedelta |
|
5 | 6 |
from urllib.parse import urljoin |
6 | 7 | |
8 |
from django.contrib.postgres.fields import ArrayField |
|
9 |
from django.conf import settings |
|
10 |
from django.core.mail import send_mail |
|
7 | 11 |
from django.db import models |
12 |
from django.template.loader import render_to_string |
|
13 |
from django.utils import timezone |
|
8 | 14 |
from django.utils.encoding import force_text |
9 | 15 |
from django.utils.translation import ugettext_lazy as _ |
10 | 16 | |
... | ... | |
69 | 75 |
msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1, |
70 | 76 |
verbose_name=_('Message class')) |
71 | 77 |
credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'), |
72 |
default=100)
|
|
78 |
default=500)
|
|
73 | 79 |
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0, editable=False) |
80 |
alert_emails = ArrayField( |
|
81 |
models.EmailField(blank=True), |
|
82 |
blank=True, |
|
83 |
null=True, |
|
84 |
verbose_name=_('Email addresses to send credit alerts to'), |
|
85 |
) |
|
86 |
credit_alert_timestamp = models.DateTimeField(null=True) |
|
74 | 87 | |
75 | 88 |
TEST_DEFAULTS = { |
76 | 89 |
'create_kwargs': { |
... | ... | |
174 | 187 |
if self.credit_left < 0: |
175 | 188 |
self.credit_left = 0 |
176 | 189 |
self.save(update_credit=False) |
177 |
if self.credit_left < self.credit_threshold_alert: |
|
178 |
ret['warning'] = 'credit level too low for %s: %s (threshold %s)' % ( |
|
179 |
self.slug, |
|
180 |
self.credit_left, |
|
181 |
self.credit_threshold_alert, |
|
182 |
) |
|
190 |
self.send_credit_alert_if_needed() |
|
183 | 191 |
ret['credit_left'] = self.credit_left |
184 | 192 |
ret['ovh_result'] = result |
185 | 193 |
ret['sms_ids'] = result.get('ids', []) |
... | ... | |
187 | 195 |
return ret |
188 | 196 | |
189 | 197 |
def update_credit_left(self): |
190 |
if not self.uses_new_api: |
|
191 |
return |
|
192 | 198 |
result = self.request('get', endpoint='') |
193 | 199 |
self.credit_left = result['creditsLeft'] |
194 | 200 |
self.save(update_credit=False) |
195 | 201 | |
202 |
def send_credit_alert_if_needed(self): |
|
203 |
if self.credit_left >= self.credit_threshold_alert: |
|
204 |
return |
|
205 |
if self.credit_alert_timestamp and self.credit_alert_timestamp > timezone.now() - timedelta(days=1): |
|
206 |
return # alerts are sent daily |
|
207 |
ctx = { |
|
208 |
'connector': self, |
|
209 |
'connector_url': urljoin(settings.SITE_BASE_URL, self.get_absolute_url()), |
|
210 |
} |
|
211 |
subject = render_to_string('ovh/credit_alert_subject.txt', ctx).strip() |
|
212 |
body = render_to_string('ovh/credit_alert_body.txt', ctx) |
|
213 |
html_body = render_to_string('ovh/credit_alert_body.html', ctx) |
|
214 |
send_mail( |
|
215 |
_('OVH SMS alert: only %s credits left') % self.credit_left, |
|
216 |
body, |
|
217 |
settings.DEFAULT_FROM_EMAIL, |
|
218 |
self.alert_emails, |
|
219 |
html_message=html_body, |
|
220 |
) |
|
221 |
self.credit_alert_timestamp = timezone.now() |
|
222 |
self.save() |
|
223 |
self.logger.warning('credit is too low, alerts were sent to %s', self.alert_emails) |
|
224 | ||
196 | 225 |
def hourly(self): |
197 | 226 |
super().hourly() |
198 |
self.update_credit_left() |
|
227 |
if self.uses_new_api: |
|
228 |
self.update_credit_left() |
|
229 |
self.send_credit_alert_if_needed() |
|
199 | 230 | |
200 | 231 |
def save(self, *args, update_credit=True, **kwargs): |
201 | 232 |
super().save(*args, **kwargs) |
202 |
if update_credit: |
|
233 |
if update_credit and self.uses_new_api:
|
|
203 | 234 |
self.add_job('update_credit_left') |
204 | 235 | |
205 | 236 |
def send_msg_legacy(self, text, sender, destinations, **kwargs): |
passerelle/apps/ovh/templates/ovh/credit_alert_body.html | ||
---|---|---|
1 |
{% extends "emails/body_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block content %} |
|
5 |
<p>{% trans "Hi," %}</p> |
|
6 | ||
7 |
<p> |
|
8 |
{% blocktrans trimmed with name=connector.title credit_left=connector.credit_left %} |
|
9 |
There are only {{ credit_left }} credits left for connector {{ name }}. |
|
10 |
{% endblocktrans %} |
|
11 |
</p> |
|
12 | ||
13 |
<p> |
|
14 |
{% blocktrans trimmed with account=connector.account %} |
|
15 |
Please add more credit as soon as possible for OVH account {{ account }}. |
|
16 |
{% endblocktrans %} |
|
17 |
</p> |
|
18 | ||
19 |
{% with _("View connector page") as button_label %} |
|
20 |
{% include "emails/button-link.html" with url=connector_url label=button_label %} |
|
21 |
{% endwith %} |
|
22 |
{% endblock %} |
passerelle/apps/ovh/templates/ovh/credit_alert_body.txt | ||
---|---|---|
1 |
{% extends "emails/body_base.txt" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block content %}{% autoescape off %}{% trans "Hi," %} |
|
5 | ||
6 |
{% blocktrans trimmed with name=connector.title credit_left=connector.credit_left %} |
|
7 |
There are only {{ credit_left }} credits left for connector {{ name }}. |
|
8 |
{% endblocktrans %} |
|
9 | ||
10 |
{% blocktrans trimmed with account=connector.account %} |
|
11 |
Please add more credit as soon as possible for OVH account {{ account }}. |
|
12 |
{% endblocktrans %} |
|
13 | ||
14 |
{% trans "View connector page:" %} {{ connector_url }} |
|
15 | ||
16 |
{% endautoescape %} |
|
17 |
{% endblock %} |
passerelle/apps/ovh/templates/ovh/credit_alert_subject.txt | ||
---|---|---|
1 |
{% extends "emails/subject.txt" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block email-subject %}{% autoescape off %}{% blocktrans trimmed with credit_left=connector.credit_left %} |
|
5 |
OVH SMS alert: only {{ credit_left }} credits left |
|
6 |
{% endblocktrans %}{% endautoescape %}{% endblock %} |
passerelle/settings.py | ||
---|---|---|
210 | 210 |
# Passerelle can receive big requests (for example base64 encoded files) |
211 | 211 |
DATA_UPLOAD_MAX_MEMORY_SIZE = 100*1024*1024 |
212 | 212 | |
213 |
SITE_BASE_URL = 'http://localhost' |
|
214 | ||
213 | 215 |
# List of passerelle.utils.Request response Content-Type to log |
214 | 216 |
LOGGED_CONTENT_TYPES_MESSAGES = ( |
215 | 217 |
r'text/', r'application/(json|xml)' |
passerelle/templates/emails/body_base.html | ||
---|---|---|
1 |
<!DOCTYPE html> |
|
2 |
<html> |
|
3 |
<head> |
|
4 |
<meta charset="utf-8"> |
|
5 |
</head> |
|
6 |
<body> |
|
7 |
<div style="max-width: 60ex;"> |
|
8 |
<div class="content"> |
|
9 |
{% block content %} |
|
10 |
{{ content }} |
|
11 |
{% endblock %} |
|
12 |
</div> |
|
13 |
</div> |
|
14 |
</body> |
|
15 |
</html> |
passerelle/templates/emails/body_base.txt | ||
---|---|---|
1 |
{% block content %}{{ content }}{% endblock %} |
passerelle/templates/emails/button-link.html | ||
---|---|---|
1 |
<a href="{{ url }}">{{ label }}</a> |
passerelle/templates/emails/subject.txt | ||
---|---|---|
1 |
{% block email-subject %}{% endblock %} |
tests/test_sms.py | ||
---|---|---|
259 | 259 |
with utils.mock_url(ovh_url, resp, 200) as mocked: |
260 | 260 |
connector.hourly() |
261 | 261 |
assert connector.credit_left == 456 |
262 | ||
263 | ||
264 |
def test_ovh_alert_emails(app, freezer, mailoutbox): |
|
265 |
connector = OVHSMSGateway.objects.create( |
|
266 |
slug='test-ovh', title='Test OVH', account='sms-test42', |
|
267 |
application_key='RHrTdU2oTsrVC0pu', |
|
268 |
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa', |
|
269 |
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz', |
|
270 |
credit_threshold_alert=100, |
|
271 |
credit_left=102, |
|
272 |
alert_emails=['test@entrouvert.org'], |
|
273 |
) |
|
274 |
api = ApiUser.objects.create(username='apiuser') |
|
275 |
obj_type = ContentType.objects.get_for_model(connector) |
|
276 |
AccessRight.objects.create(codename='can_send_messages', apiuser=api, resource_type=obj_type, |
|
277 |
resource_pk=connector.pk) |
|
278 | ||
279 |
payload = { |
|
280 |
'message': 'hello', |
|
281 |
'from': '+33699999999', |
|
282 |
'to': ['+33688888888', '+33677777777'], |
|
283 |
} |
|
284 | ||
285 |
# register job |
|
286 |
freezer.move_to('2019-01-01 00:00:00') |
|
287 |
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug) |
|
288 |
app.post_json(path, params=payload) |
|
289 | ||
290 |
# perform job |
|
291 |
freezer.move_to('2019-01-01 01:00:03') |
|
292 |
resp = { |
|
293 |
'totalCreditsRemoved': 1, |
|
294 |
} |
|
295 |
base_url = connector.API_URL % {'serviceName': 'sms-test42'} |
|
296 |
url = base_url + 'jobs/' |
|
297 |
with utils.mock_url(url, resp, 200) as mocked: |
|
298 |
connector.jobs() |
|
299 | ||
300 |
assert connector.credit_left == 101 |
|
301 |
assert len(mailoutbox) == 0 |
|
302 | ||
303 |
# register job |
|
304 |
app.post_json(path, params=payload) |
|
305 | ||
306 |
# perform job |
|
307 |
freezer.move_to('2019-01-01 01:00:06') |
|
308 |
resp = { |
|
309 |
'totalCreditsRemoved': 2, |
|
310 |
} |
|
311 |
with utils.mock_url(url, resp, 200) as mocked: |
|
312 |
connector.jobs() |
|
313 | ||
314 |
mail = mailoutbox[0] |
|
315 |
assert mail.recipients() == ['test@entrouvert.org'] |
|
316 |
assert mail.subject == 'OVH SMS alert: only 99 credits left' |
|
317 |
for body in (mail.body, mail.alternatives[0][0]): |
|
318 |
assert connector.account in body |
|
319 |
assert connector.title in body |
|
320 |
assert 'http://localhost/ovh/test-ovh/' in body |
|
321 |
mailoutbox.clear() |
|
322 | ||
323 |
# alert should not be send with every SMS |
|
324 |
app.post_json(path, params=payload) |
|
325 |
freezer.move_to('2019-01-01 01:00:09') |
|
326 |
with utils.mock_url(url, resp, 200) as mocked: |
|
327 |
connector.jobs() |
|
328 |
assert len(mailoutbox) == 0 |
|
329 | ||
330 |
# alert is sent again daily |
|
331 |
resp = {'creditsLeft': 99} |
|
332 |
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'} |
|
333 |
with utils.mock_url(ovh_url, resp, 200) as mocked: |
|
334 |
connector.hourly() |
|
335 |
assert len(mailoutbox) == 0 |
|
336 | ||
337 |
freezer.move_to('2019-01-02 01:00:07') |
|
338 |
with utils.mock_url(ovh_url, resp, 200) as mocked: |
|
339 |
connector.hourly() |
|
340 |
assert len(mailoutbox) == 1 |
|
262 |
- |