Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ami/agent_admin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class NotificationForm(AMIDsfrBaseForm):
content_body = forms.CharField(
widget=forms.Textarea(attrs={"rows": 4}),
)
content_private_body = forms.CharField(
widget=forms.Textarea(attrs={"rows": 4}),
required=False,
)
content_icon = forms.CharField(
required=False,
)
Expand Down
1 change: 1 addition & 0 deletions ami/agent_admin/tests/manage/test_send_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def test_send_notification(app, notifications_agent: Agent) -> None:
assert response.forms["send-notification"]["recipient_fc_hash"].value == ""
assert response.forms["send-notification"]["content_title"].value == ""
assert response.forms["send-notification"]["content_body"].value == ""
assert response.forms["send-notification"]["content_private_body"].value == ""
assert response.forms["send-notification"]["content_icon"].value == ""
assert response.forms["send-notification"]["item_type"].value == ""
assert response.forms["send-notification"]["item_id"].value == ""
Expand Down
5 changes: 4 additions & 1 deletion ami/followup/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def from_notifications(cls, notifications: list[Notification]) -> Self | None:
first_notification = notifications[0]
last_notification = notifications[-1]
external_urls = [n.item_external_url for n in notifications if n.item_external_url]
description = last_notification.content_body
if last_notification.content_private_body:
description += f"\n\n{last_notification.content_private_body}"
try:
status_id = ItemGenericStatus(last_notification.item_generic_status)
except ValueError:
Expand All @@ -49,7 +52,7 @@ def from_notifications(cls, notifications: list[Notification]) -> Self | None:
milestone_start_date=last_notification.item_milestone_start_date,
milestone_end_date=last_notification.item_milestone_end_date,
title=last_notification.content_title,
description=last_notification.content_body,
description=description,
external_url=external_urls[-1] if external_urls else None,
created_at=first_notification.send_date,
updated_at=last_notification.send_date,
Expand Down
24 changes: 24 additions & 0 deletions ami/followup/tests/test_notification_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,33 @@ def test_get_notifications_data(user: User) -> None:
partner_id="dinum-ami",
)

notification8 = Notification.objects.create(
user_id=user.id,
content_body="other notification",
content_private_body="some private body content",
content_title="Other Notification title",
item_generic_status="closed",
item_status_label="Validé",
item_type="Other",
item_id="52",
partner_id="dinum-ami",
)

result = get_notifications_data(current_user=user)

assert result == [
FollowUpInventoryItem(
external_id="dinum-ami:Other:52",
status_id=ItemGenericStatus.CLOSED,
status_label="Validé",
milestone_start_date=None,
milestone_end_date=None,
title="Other Notification title",
description="other notification\n\nsome private body content",
external_url=None,
created_at=notification8.send_date,
updated_at=notification8.send_date,
),
FollowUpInventoryItem(
external_id="dinum-ami:Other:42",
status_id=ItemGenericStatus.CLOSED,
Expand Down
22 changes: 22 additions & 0 deletions ami/notification/migrations/0010_add_content_private_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 6.0.5 on 2026-05-19 13:30

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("notification", "0009_partner_id"),
]

operations = [
migrations.AddField(
model_name="notification",
name="content_private_body",
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name="schedulednotification",
name="content_private_body",
field=models.CharField(blank=True, null=True),
),
]
3 changes: 3 additions & 0 deletions ami/notification/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Notification(models.Model):

content_title = models.CharField()
content_body = models.CharField()
content_private_body = models.CharField(blank=True, null=True)
content_icon = models.CharField(blank=True, null=True)

item_type = models.CharField(blank=True, null=True)
Expand Down Expand Up @@ -62,6 +63,7 @@ class ScheduledNotification(models.Model):

content_title = models.CharField()
content_body = models.CharField()
content_private_body = models.CharField(blank=True, null=True)
content_icon = models.CharField()

internal_url = models.CharField(blank=True, null=True)
Expand All @@ -79,6 +81,7 @@ def build_notification(self) -> Notification:
user=self.user,
content_title=self.content_title,
content_body=self.content_body,
content_private_body=self.content_private_body,
content_icon=self.content_icon,
internal_url=self.internal_url,
partner_id="dinum-ami",
Expand Down
7 changes: 7 additions & 0 deletions ami/notification/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class NotificationSerializer(serializers.ModelSerializer):
class Meta:
fields = [
"content_body",
"content_private_body",
"content_icon",
"content_title",
"created_at",
Expand All @@ -40,6 +41,7 @@ class Meta:
class ScheduledNotificationCreateSerializer(serializers.Serializer):
content_title = serializers.CharField(allow_blank=False)
content_body = serializers.CharField(allow_blank=False)
content_private_body = serializers.CharField(allow_blank=True, default=None)
content_icon = serializers.CharField(allow_blank=False)
reference = serializers.CharField(allow_blank=False)
internal_url = serializers.CharField(allow_blank=False, default=None)
Expand Down Expand Up @@ -67,6 +69,11 @@ class PartnerNotificationCreateSerializer(serializers.Serializer):
allow_blank=False,
help_text="Contenu de la notification",
)
content_private_body = serializers.CharField(
allow_blank=True,
default=None,
help_text="Contenu privé de la notification qui ne sera pas 'push'é",
)
content_icon = serializers.CharField(
default=None,
help_text="Nom technique de l'icône à associer à la notification dans l'application AMI, "
Expand Down
4 changes: 4 additions & 0 deletions ami/notification/tests/api/test_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def test_get_notifications(
other_notification = Notification.objects.create(
user=notification.user,
content_body="Other notification",
content_private_body="some private body content",
content_title="Notification title",
)

Expand All @@ -56,6 +57,7 @@ def test_get_notifications(
"user_id": str(other_notification.user.id),
"content_title": "Notification title",
"content_body": "Other notification",
"content_private_body": "some private body content",
"content_icon": None,
"item_type": None,
"item_id": None,
Expand All @@ -73,6 +75,7 @@ def test_get_notifications(
"user_id": str(notification.user.id),
"content_title": "Notification title",
"content_body": "Hello notification",
"content_private_body": None,
"content_icon": None,
"item_type": None,
"item_id": None,
Expand Down Expand Up @@ -141,6 +144,7 @@ def test_read_notification(
"user_id": str(notification.user.id),
"content_title": "Notification title",
"content_body": "Hello notification",
"content_private_body": None,
"content_icon": None,
"item_type": None,
"item_id": None,
Expand Down
20 changes: 19 additions & 1 deletion ami/notification/tests/api/test_partner_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from unittest.mock import Mock

import pytest
import webpush as webpush_lib
from asgiref.sync import sync_to_async
from channels.testing.websocket import WebsocketCommunicator
from pytest_httpx import HTTPXMock
Expand All @@ -23,13 +24,26 @@ async def test_create_webpush_notification(
partner_auth: dict[str, str],
httpx_mock: HTTPXMock,
websocket: WebsocketCommunicator,
monkeypatch: pytest.MonkeyPatch,
) -> None:
# Make sure we don't even try sending a notification to a push server.
httpx_mock.add_response(url=webpush_registration.subscription["endpoint"])

# Capture the plaintext message passed to the webpush provider before encryption.
captured_push_messages: list[str] = []
original_webpush_get = webpush_lib.WebPush.get

def capturing_webpush_get(self, message, **kwargs):
captured_push_messages.append(message)
return original_webpush_get(self, message=message, **kwargs)

monkeypatch.setattr(webpush_lib.WebPush, "get", capturing_webpush_get)

notification_data = {
"recipient_fc_hash": webpush_registration.user.fc_hash,
"content_title": "Brouillon de nouvelle demande de démarche d'OTV",
"content_body": "Merci d'avoir initié votre demande",
"content_private_body": "Ceci est privé et ne devrait jamais être `push`é",
"content_icon": "foo",
"item_type": "OTV",
"item_id": "A-5-JGBJ5VMOY",
Expand All @@ -54,6 +68,7 @@ async def test_create_webpush_notification(
)
assert notification2.user.id == webpush_registration.user.id
assert notification2.content_body == "Merci d'avoir initié votre demande"
assert notification2.content_private_body == "Ceci est privé et ne devrait jamais être `push`é"
assert notification2.content_title == "Brouillon de nouvelle demande de démarche d'OTV"
assert notification2.content_icon == "foo"
assert notification2.item_type == "OTV"
Expand Down Expand Up @@ -85,7 +100,10 @@ async def test_create_webpush_notification(
"id": str(notification2.id),
"event": "created",
}
assert httpx_mock.get_request()
request = httpx_mock.get_request()
assert request is not None
assert len(captured_push_messages) == 1
assert notification_data["content_private_body"] not in captured_push_messages[0]


@pytest.mark.django_db
Expand Down
10 changes: 10 additions & 0 deletions ami/notification/tests/api/test_scheduled_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def test_create_scheduled_notification(app, user: User) -> None:
scheduled_notification_data = {
"content_title": "title",
"content_body": "body",
"content_private_body": "private content body",
"content_icon": "icon",
"reference": "reference",
"internal_url": "internal-url",
Expand All @@ -31,6 +32,7 @@ def test_create_scheduled_notification(app, user: User) -> None:
assert scheduled_notification.user.id == user.id
assert scheduled_notification.content_title == "title"
assert scheduled_notification.content_body == "body"
assert scheduled_notification.content_private_body == "private content body"
assert scheduled_notification.content_icon == "icon"
assert scheduled_notification.reference == "reference"
assert scheduled_notification.internal_url == "internal-url"
Expand All @@ -47,6 +49,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
user_id=user.id,
content_title="title",
content_body="body",
content_private_body="private body",
content_icon="icon",
reference="reference",
internal_url="internal-url",
Expand All @@ -57,6 +60,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
scheduled_notification_data = {
"content_title": "title-updated",
"content_body": "body-updated",
"content_private_body": "private-body-updated",
"content_icon": "icon-updated",
"reference": "reference",
"internal_url": "internal-url-updated",
Expand All @@ -71,6 +75,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
assert scheduled_notification.user.id == user.id
assert scheduled_notification.content_title == "title-updated"
assert scheduled_notification.content_body == "body-updated"
assert scheduled_notification.content_private_body == "private-body-updated"
assert scheduled_notification.content_icon == "icon-updated"
assert scheduled_notification.reference == "reference"
assert scheduled_notification.scheduled_at == scheduled_notification_date
Expand All @@ -85,6 +90,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
scheduled_notification_data = {
"content_title": "title-updated-again",
"content_body": "body-updated-again",
"content_private_body": "private-body-updated-again",
"content_icon": "icon-updated-again",
"reference": "reference",
"scheduled_at": scheduled_notification_date2.isoformat(),
Expand All @@ -98,6 +104,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
assert scheduled_notification.user.id == user.id
assert scheduled_notification.content_title == "title-updated"
assert scheduled_notification.content_body == "body-updated"
assert scheduled_notification.content_private_body == "private-body-updated"
assert scheduled_notification.content_icon == "icon-updated"
assert scheduled_notification.reference == "reference"
assert scheduled_notification.internal_url == "internal-url-updated"
Expand All @@ -111,6 +118,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
user_id=other_user.id,
content_title="title",
content_body="body",
content_private_body="private body",
content_icon="icon",
reference="other-reference",
scheduled_at=now(),
Expand All @@ -119,6 +127,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
scheduled_notification_data = {
"content_title": "title",
"content_body": "body",
"content_private_body": "private body",
"content_icon": "icon",
"reference": "other-reference",
"scheduled_at": scheduled_notification_date.isoformat(),
Expand All @@ -132,6 +141,7 @@ def test_create_scheduled_notification_known_reference(app, user: User) -> None:
assert scheduled_notification.user.id == user.id
assert scheduled_notification.content_title == "title"
assert scheduled_notification.content_body == "body"
assert scheduled_notification.content_private_body == "private body"
assert scheduled_notification.content_icon == "icon"
assert scheduled_notification.reference == "other-reference"
assert scheduled_notification.internal_url is None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ async def test_command_publish_scheduled_notifications(
user_id=user.id,
content_title="title 1",
content_body="body 1",
content_private_body="private body 1",
content_icon="icon 1",
reference="reference 1",
internal_url="internal-url-1",
Expand All @@ -40,6 +41,7 @@ async def test_command_publish_scheduled_notifications(
user_id=user.id,
content_title="title 2",
content_body="body 2",
content_private_body="private body 2",
content_icon="icon 2",
reference="reference 2",
internal_url="internal-url-2",
Expand All @@ -49,6 +51,7 @@ async def test_command_publish_scheduled_notifications(
user_id=user.id,
content_title="title 3",
content_body="body 3",
content_private_body="private body 3",
content_icon="icon 3",
reference="reference 3",
internal_url="internal-url-3",
Expand All @@ -72,6 +75,7 @@ async def test_command_publish_scheduled_notifications(
assert notification.user_id == user.id # type: ignore[attr-defined]
assert notification.content_title == "title 3"
assert notification.content_body == "body 3"
assert notification.content_private_body == "private body 3"
assert notification.content_icon == "icon 3"
assert notification.item_type is None
assert notification.item_id is None
Expand Down
1 change: 1 addition & 0 deletions ami/replication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class AnonymizedNotification(AnonymizedModel):
id = models.UUIDField(editable=False)
content_title = models.CharField()
content_body = models.CharField()
# No `content_private_body` here.
content_icon = models.CharField(blank=True, null=True)

item_type = models.CharField(blank=True, null=True)
Expand Down
Loading