0002-notification-send-a-notification-to-several-users-60.patch
combo/apps/notifications/api_views.py | ||
---|---|---|
9 | 9 |
# This program is distributed in the hope that it will be useful, |
10 | 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU Affero General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from django.contrib.auth.models import User |
|
18 |
from django.db import transaction |
|
17 | 19 |
from django.utils.encoding import force_text |
18 | 20 |
from rest_framework import authentication, permissions, serializers, status |
19 | 21 |
from rest_framework.generics import GenericAPIView |
20 | 22 |
from rest_framework.response import Response |
21 | 23 | |
24 |
from combo.profile.utils import get_user_from_name_id |
|
25 | ||
22 | 26 |
from .models import Notification |
23 | 27 | |
24 | 28 | |
25 | 29 |
class NotificationSerializer(serializers.Serializer): |
26 | 30 |
summary = serializers.CharField(required=True, allow_blank=False, max_length=140) |
27 | 31 |
id = serializers.CharField(required=False, allow_null=True) |
28 | 32 |
body = serializers.CharField(allow_blank=False, default='') |
29 | 33 |
url = serializers.URLField(allow_blank=True, default='') |
30 | 34 |
origin = serializers.CharField(allow_blank=True, default='') |
31 | 35 |
start_timestamp = serializers.DateTimeField(required=False, allow_null=True) |
32 | 36 |
end_timestamp = serializers.DateTimeField(required=False, allow_null=True) |
33 | 37 |
duration = serializers.IntegerField(required=False, allow_null=True, min_value=0) |
38 |
name_ids = serializers.ListField( |
|
39 |
required=False, child=serializers.CharField(required=True, max_length=150) |
|
40 |
) |
|
41 | ||
42 |
def validate(self, attrs): |
|
43 |
super().validate(attrs) |
|
44 |
users = [] |
|
45 |
founded = [] |
|
46 |
missing = [] |
|
47 |
for uuid in attrs.get('name_ids') or []: |
|
48 |
try: |
|
49 |
user = get_user_from_name_id(uuid, raise_on_missing=True) |
|
50 |
except User.DoesNotExist: |
|
51 |
missing.append(uuid) |
|
52 |
else: |
|
53 |
users.append(user) |
|
54 |
founded.append(uuid) |
|
55 |
attrs['users'] = users |
|
56 |
attrs['founded_uuids'] = founded |
|
57 |
attrs['missing_uuids'] = missing |
|
58 |
return attrs |
|
34 | 59 | |
35 | 60 | |
36 | 61 |
class Add(GenericAPIView): |
37 | 62 |
permission_classes = (permissions.IsAuthenticated,) |
38 | 63 |
serializer_class = NotificationSerializer |
39 | 64 | |
40 | 65 |
def post(self, request, *args, **kwargs): |
41 | 66 |
serializer = self.get_serializer(data=request.data) |
42 | 67 |
if not serializer.is_valid(): |
43 | 68 |
response = {'err': 1, 'err_desc': serializer.errors} |
44 | 69 |
return Response(response, status.HTTP_400_BAD_REQUEST) |
45 |
data = serializer.validated_data |
|
70 |
payload = serializer.validated_data |
|
71 |
notification_ids = [] |
|
46 | 72 |
try: |
47 |
notification = Notification.notify( |
|
48 |
user=request.user, |
|
49 |
summary=data['summary'], |
|
50 |
id=data.get('id'), |
|
51 |
body=data.get('body'), |
|
52 |
url=data.get('url'), |
|
53 |
origin=data.get('origin'), |
|
54 |
start_timestamp=data.get('start_timestamp'), |
|
55 |
end_timestamp=data.get('end_timestamp'), |
|
56 |
duration=data.get('duration'), |
|
57 |
) |
|
73 |
with transaction.atomic(): |
|
74 |
for user in payload.get('users') or [request.user]: |
|
75 |
notification = Notification.notify( |
|
76 |
user=user, |
|
77 |
summary=payload['summary'], |
|
78 |
id=payload.get('id'), |
|
79 |
body=payload.get('body'), |
|
80 |
url=payload.get('url'), |
|
81 |
origin=payload.get('origin'), |
|
82 |
start_timestamp=payload.get('start_timestamp'), |
|
83 |
end_timestamp=payload.get('end_timestamp'), |
|
84 |
duration=payload.get('duration'), |
|
85 |
) |
|
86 |
notification_ids.append(notification.public_id) |
|
58 | 87 |
except ValueError as e: |
59 | 88 |
response = {'err': 1, 'err_desc': {'id': [force_text(e)]}} |
60 | 89 |
return Response(response, status.HTTP_400_BAD_REQUEST) |
90 | ||
91 |
data = {} |
|
92 |
if not payload.get('name_ids'): |
|
93 |
data = {'id': notification_ids.pop()} |
|
61 | 94 |
else: |
62 |
response = {'err': 0, 'data': {'id': notification.public_id}} |
|
63 |
return Response(response) |
|
95 |
data = { |
|
96 |
'nb_notify': len(notification_ids), |
|
97 |
'notified': payload.get('founded_uuids'), |
|
98 |
'unnotified': payload.get('missing_uuids'), |
|
99 |
} |
|
100 |
if payload.get('id'): |
|
101 |
data['id'] = payload.get('id') |
|
102 |
else: |
|
103 |
data['ids'] = notification_ids |
|
104 |
return Response({'err': 0, 'data': data}) |
|
64 | 105 | |
65 | 106 | |
66 | 107 |
add = Add.as_view() |
67 | 108 | |
68 | 109 | |
69 | 110 |
class Ack(GenericAPIView): |
70 | 111 |
authentication_classes = (authentication.SessionAuthentication,) |
71 | 112 |
permission_classes = (permissions.IsAuthenticated,) |
tests/test_notification.py | ||
---|---|---|
5 | 5 | |
6 | 6 |
import pytest |
7 | 7 |
from django.contrib.auth.models import User |
8 | 8 |
from django.test import Client |
9 | 9 |
from django.test.client import RequestFactory |
10 | 10 |
from django.urls import reverse |
11 | 11 |
from django.utils.encoding import force_text |
12 | 12 |
from django.utils.timezone import now |
13 |
from mellon.models import UserSAMLIdentifier |
|
13 | 14 | |
14 | 15 |
from combo.apps.lingo.models import ActiveItems, PaymentBackend, Regie |
15 | 16 |
from combo.apps.notifications.models import Notification, NotificationsCell |
16 | 17 |
from combo.data.models import Page |
17 | 18 | |
18 | 19 |
from .test_manager import login as login_app |
19 | 20 | |
20 | 21 |
pytestmark = pytest.mark.django_db |
... | ... | |
513 | 514 |
Notification.objects.visible(john_doe).ack() |
514 | 515 | |
515 | 516 |
# acking a notification without and end_timestamp, still visible |
516 | 517 |
freezer.move_to(start + timedelta(days=365, seconds=1)) |
517 | 518 |
content = cell.render(context) |
518 | 519 |
assert Notification.objects.visible(john_doe).count() == 1 |
519 | 520 |
assert 'notibar' in content |
520 | 521 |
assert 'notifoo' not in content |
522 | ||
523 | ||
524 |
def test_notification_ws_create_many(john_doe, jane_doe): |
|
525 |
UserSAMLIdentifier.objects.create(user=john_doe, name_id='00a') |
|
526 |
UserSAMLIdentifier.objects.create(user=jane_doe, name_id='00b') |
|
527 |
login(john_doe) |
|
528 | ||
529 |
data = {'summary': 'foo', 'name_ids': ['00a', '00b']} |
|
530 |
resp = client.post(reverse('api-notification-add'), json.dumps(data), content_type='application/json') |
|
531 |
assert resp.status_code == 200 |
|
532 |
assert len(resp.json()['data']['ids']) == 2 |
|
533 |
assert Notification.objects.count() == 2 |
|
534 |
assert Notification.objects.get(user=john_doe, id=resp.json()['data']['ids'][0]).summary == 'foo' |
|
535 |
assert Notification.objects.get(user=jane_doe, id=resp.json()['data']['ids'][1]).summary == 'foo' |
|
536 |
resp.json()['data']['ids'] = ['xx', 'yy'] |
|
537 |
assert resp.json() == { |
|
538 |
'err': 0, |
|
539 |
'data': {'nb_notify': 2, 'notified': ['00a', '00b'], 'unnotified': [], 'ids': ['xx', 'yy']}, |
|
540 |
} |
|
541 | ||
542 |
# wrong username |
|
543 |
data = {'summary': 'bar', 'name_ids': ['00a', 'unknown']} |
|
544 |
resp = client.post(reverse('api-notification-add'), json.dumps(data), content_type='application/json') |
|
545 |
assert resp.status_code == 200 |
|
546 |
assert len(resp.json()['data']['ids']) == 1 |
|
547 |
assert Notification.objects.count() == 3 |
|
548 |
assert Notification.objects.get(user=john_doe, id=resp.json()['data']['ids'][0]).summary == 'bar' |
|
549 |
resp.json()['data']['ids'] = ['xx'] |
|
550 |
assert resp.json() == { |
|
551 |
'err': 0, |
|
552 |
'data': {'nb_notify': 1, 'notified': ['00a'], 'unnotified': ['unknown'], 'ids': ['xx']}, |
|
553 |
} |
|
554 | ||
555 | ||
556 |
def test_notification_ws_update_many(john_doe, jane_doe): |
|
557 |
UserSAMLIdentifier.objects.create(user=john_doe, name_id='00a') |
|
558 |
UserSAMLIdentifier.objects.create(user=jane_doe, name_id='00b') |
|
559 |
login(john_doe) |
|
560 | ||
561 |
data = { |
|
562 |
'summary': 'foo', |
|
563 |
'name_ids': ['00a', '00b'], |
|
564 |
'id': 'test:42', |
|
565 |
} |
|
566 |
resp = client.post(reverse('api-notification-add'), json.dumps(data), content_type='application/json') |
|
567 |
assert resp.status_code == 200 |
|
568 |
result = resp.json() |
|
569 |
assert resp.json() == { |
|
570 |
'err': 0, |
|
571 |
'data': {'nb_notify': 2, 'notified': ['00a', '00b'], 'unnotified': [], 'id': 'test:42'}, |
|
572 |
} |
|
573 |
assert Notification.objects.count() == 2 |
|
574 |
assert Notification.objects.get(user=john_doe, external_id='test:42').summary == 'foo' |
|
575 |
assert Notification.objects.get(user=jane_doe, external_id='test:42').summary == 'foo' |
|
576 | ||
577 |
data['summary'] = 'bar' |
|
578 |
resp = client.post(reverse('api-notification-add'), json.dumps(data), content_type='application/json') |
|
579 |
assert resp.status_code == 200 |
|
580 |
assert resp.json() == { |
|
581 |
'err': 0, |
|
582 |
'data': {'nb_notify': 2, 'notified': ['00a', '00b'], 'unnotified': [], 'id': 'test:42'}, |
|
583 |
} |
|
584 |
result = resp.json() |
|
585 |
assert result['err'] == 0 |
|
586 |
assert result['data']['nb_notify'] == 2 |
|
587 |
assert result['data']['id'] == 'test:42' |
|
588 |
assert Notification.objects.count() == 2 |
|
589 |
assert Notification.objects.get(user=john_doe, external_id='test:42').summary == 'bar' |
|
590 |
assert Notification.objects.get(user=jane_doe, external_id='test:42').summary == 'bar' |
|
521 |
- |