fix(webpush): support Django lazy translation objects in push payload JSON serialization

This commit is contained in:
kdeterme 2026-09-04 19:49:12 +02:00
parent 8385a9e3e2
commit 135dfb41e6
2 changed files with 16 additions and 12 deletions

View file

@ -312,14 +312,16 @@ class WebPushTests(TestCase):
mock_resp = MagicMock() mock_resp = MagicMock()
mock_resp.status_code = 201 mock_resp.status_code = 201
from django.utils.translation import gettext_lazy as _
with patch('notifications.webpush.requests.post', return_value=mock_resp) as mock_post: with patch('notifications.webpush.requests.post', return_value=mock_resp) as mock_post:
send_webpush_to_user( res = send_webpush_to_user(
user=self.user, user=self.user,
title="Alerte Intervention", title=_("Alerte Intervention"),
body="Nouvelle intervention assignée", body=_("Nouvelle intervention assignée"),
action_url="/interventions/1/", action_url="/interventions/1/",
async_send=False async_send=False
) )
self.assertEqual(res.get('success'), 1)
mock_post.assert_called_once() mock_post.assert_called_once()
called_endpoint = mock_post.call_args[0][0] called_endpoint = mock_post.call_args[0][0]
self.assertEqual(called_endpoint, sub.endpoint) self.assertEqual(called_endpoint, sub.endpoint)

View file

@ -12,7 +12,9 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from django.conf import settings from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_str
from py_vapid import Vapid, sign as vapid_sign from py_vapid import Vapid, sign as vapid_sign
import requests import requests
@ -215,7 +217,7 @@ def _send_single_push(subscription: WebPushSubscription, payload: dict, vapid_ob
receiver_pub_bytes = b64_urlsafe_decode(p256dh) receiver_pub_bytes = b64_urlsafe_decode(p256dh)
auth_secret = b64_urlsafe_decode(auth) auth_secret = b64_urlsafe_decode(auth)
payload_bytes = json.dumps(payload).encode('utf-8') payload_bytes = json.dumps(payload, cls=DjangoJSONEncoder).encode('utf-8')
encrypted_body = encrypt_webpush_rfc8291(payload_bytes, receiver_pub_bytes, auth_secret) encrypted_body = encrypt_webpush_rfc8291(payload_bytes, receiver_pub_bytes, auth_secret)
vapid_headers = get_vapid_headers(endpoint, vapid_obj) vapid_headers = get_vapid_headers(endpoint, vapid_obj)
@ -276,14 +278,14 @@ def send_webpush_to_user(
return {'success': 0, 'failed': 0, 'errors': ['Clé VAPID non configurée']} return {'success': 0, 'failed': 0, 'errors': ['Clé VAPID non configurée']}
payload = { payload = {
'title': title, 'title': force_str(title),
'body': body or '', 'body': force_str(body or ''),
'icon': icon or '/static/common/android-192.png', 'icon': icon or '/static/common/android-192.png',
'badge': badge or '/static/common/favicon-48.png', 'badge': badge or '/static/common/favicon-48.png',
'action_url': action_url or '/notifications/', 'action_url': force_str(action_url or '/notifications/'),
'tag': tag or f"loko-{int(timezone.now().timestamp())}", 'tag': tag or f"loko-{int(timezone.now().timestamp())}",
'data': { 'data': {
'url': action_url or '/notifications/', 'url': force_str(action_url or '/notifications/'),
**(extra_data or {}), **(extra_data or {}),
}, },
} }
@ -336,14 +338,14 @@ def send_webpush_to_users(
return {'success': 0, 'failed': 0, 'errors': ['Clé VAPID non configurée']} return {'success': 0, 'failed': 0, 'errors': ['Clé VAPID non configurée']}
payload = { payload = {
'title': title, 'title': force_str(title),
'body': body or '', 'body': force_str(body or ''),
'icon': icon or '/static/common/android-192.png', 'icon': icon or '/static/common/android-192.png',
'badge': badge or '/static/common/favicon-48.png', 'badge': badge or '/static/common/favicon-48.png',
'action_url': action_url or '/notifications/', 'action_url': force_str(action_url or '/notifications/'),
'tag': tag or f"loko-{int(timezone.now().timestamp())}", 'tag': tag or f"loko-{int(timezone.now().timestamp())}",
'data': { 'data': {
'url': action_url or '/notifications/', 'url': force_str(action_url or '/notifications/'),
**(extra_data or {}), **(extra_data or {}),
}, },
} }