Skip to content

Commit 5d76005

Browse files
committed
Add folding functionality for CSV imports
1 parent d00bac3 commit 5d76005

File tree

2 files changed

+156
-2
lines changed

2 files changed

+156
-2
lines changed

onadata/apps/messaging/serializers.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from actstream.signals import action
1818
from rest_framework import exceptions, serializers
1919

20-
from onadata.apps.messaging.constants import MESSAGE, MESSAGE_VERBS
20+
from onadata.apps.messaging.constants import MESSAGE, MESSAGE_VERBS, SUBMISSION_CREATED
2121
from onadata.apps.messaging.utils import TargetDoesNotExist, get_target
2222
from onadata.libs.utils.common_tools import report_exception
2323

@@ -108,6 +108,42 @@ def create(self, validated_data):
108108
% target_object
109109
)
110110
raise exceptions.PermissionDenied(detail=message)
111+
112+
# Check if we should fold this message into an existing action
113+
a = Action.objects.filter(
114+
target_content_type=content_type, target_object_id=target_id
115+
).first()
116+
if a is not None and a.verb == SUBMISSION_CREATED:
117+
a_json = json.loads(a.description)
118+
description = a_json.get("description")
119+
a_ids = a_json.get("id", [])
120+
message_id_limit = getattr(settings, "NOTIFICATION_ID_LIMIT", 100)
121+
122+
# Parse the incoming description to check if it's also imported_via_csv
123+
description_data = validated_data.get("description")
124+
if isinstance(description_data, str):
125+
description_data = json.loads(description_data)
126+
incoming_description = (
127+
description_data.get("description")
128+
if isinstance(description_data, dict)
129+
else None
130+
)
131+
132+
if (
133+
description == "imported_via_csv"
134+
and incoming_description == "imported_via_csv"
135+
and len(a_ids) < message_id_limit
136+
):
137+
submission_id = description_data.get("id")
138+
139+
# Add the new submission ID to the existing list
140+
if submission_id is not None:
141+
a_ids.append(submission_id)
142+
a_json["id"] = a_ids
143+
a.description = json.dumps(a_json)
144+
a.save()
145+
return a
146+
111147
results = action.send(
112148
request.user,
113149
verb=verb,

onadata/apps/messaging/tests/test_serializers.py

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
Tests for messaging serializers.
44
"""
55

6+
import json
67
from unittest.mock import MagicMock, patch
78

89
from django.contrib.auth import get_user_model
10+
from django.contrib.contenttypes.models import ContentType
911
from django.test import TestCase
1012

13+
from actstream.models import Action
14+
from rest_framework.test import APIRequestFactory
15+
16+
from onadata.apps.main.tests.test_base import TestBase
1117
from onadata.apps.messaging.constants import EXPORT_CREATED, SUBMISSION_CREATED, XFORM
12-
from onadata.apps.messaging.serializers import send_message
18+
from onadata.apps.messaging.serializers import MessageSerializer, send_message
1319

1420
User = get_user_model()
1521

@@ -100,3 +106,115 @@ def test_send_message_with_list_of_ids(self):
100106
call_args = mock_serializer.call_args
101107
self.assertEqual(call_args[1]["data"]["message"], '{"id": [1, 2, 3]}')
102108
mock_instance.save.assert_called_once()
109+
110+
111+
class TestMessageFolding(TestBase):
112+
"""
113+
Test message folding functionality for imported_via_csv submissions.
114+
"""
115+
116+
def setUp(self):
117+
super().setUp()
118+
self.factory = APIRequestFactory()
119+
self._create_user_and_login()
120+
self._publish_transportation_form()
121+
self.xform_content_type = ContentType.objects.get_for_model(self.xform)
122+
123+
def test_message_folding_when_ids_below_limit(self):
124+
"""
125+
Test that messages are folded into an existing action when:
126+
- An existing Action exists for the XForm
127+
- The action's verb is SUBMISSION_CREATED
128+
- The description is 'imported_via_csv'
129+
- The number of IDs is less than 100 (NOTIFICATION_ID_LIMIT)
130+
"""
131+
# Create an initial action with the right conditions (50 IDs, below limit)
132+
ids_list = list(range(1, 51))
133+
initial_description = json.dumps(
134+
{"id": ids_list, "description": "imported_via_csv"}
135+
)
136+
initial_action = Action.objects.create(
137+
actor_content_type=self.xform_content_type,
138+
actor_object_id=self.xform.pk,
139+
verb=SUBMISSION_CREATED,
140+
target_content_type=self.xform_content_type,
141+
target_object_id=self.xform.pk,
142+
description=initial_description,
143+
)
144+
145+
# Create a new message that should be folded into the existing one
146+
view_data = {
147+
"message": json.dumps({"id": 51, "description": "imported_via_csv"}),
148+
"target_id": self.xform.pk,
149+
"target_type": XFORM,
150+
"verb": SUBMISSION_CREATED,
151+
}
152+
request = self.factory.post("/messaging", view_data)
153+
request.user = self.user
154+
155+
serializer = MessageSerializer(data=view_data, context={"request": request})
156+
self.assertTrue(serializer.is_valid())
157+
result = serializer.save()
158+
159+
# Verify that the same action was returned (folded)
160+
self.assertEqual(result.id, initial_action.id)
161+
162+
# Verify that the new submission ID was added to the existing action
163+
updated_action = Action.objects.get(id=initial_action.id)
164+
updated_description = json.loads(updated_action.description)
165+
self.assertEqual(updated_description["description"], "imported_via_csv")
166+
self.assertIn(51, updated_description["id"])
167+
self.assertEqual(len(updated_description["id"]), 51)
168+
169+
# Verify that only one action exists for this xform
170+
self.assertEqual(
171+
Action.objects.filter(
172+
target_content_type=self.xform_content_type,
173+
target_object_id=self.xform.pk,
174+
).count(),
175+
1,
176+
)
177+
178+
def test_no_folding_when_conditions_not_met(self):
179+
"""
180+
Test that a new action is created (no folding) when conditions are not met:
181+
- ID count at or above limit (100)
182+
- Wrong verb (not SUBMISSION_CREATED)
183+
- Wrong description (not 'imported_via_csv')
184+
- No existing action
185+
"""
186+
# Test 1: ID count at limit - should NOT fold
187+
ids_list = list(range(1, 101)) # 100 IDs at the limit
188+
initial_description = json.dumps(
189+
{"id": ids_list, "description": "imported_via_csv"}
190+
)
191+
Action.objects.create(
192+
actor_content_type=self.xform_content_type,
193+
actor_object_id=self.xform.pk,
194+
verb=SUBMISSION_CREATED,
195+
target_content_type=self.xform_content_type,
196+
target_object_id=self.xform.pk,
197+
description=initial_description,
198+
)
199+
200+
view_data = {
201+
"message": json.dumps({"id": 101, "description": "imported_via_csv"}),
202+
"target_id": self.xform.pk,
203+
"target_type": XFORM,
204+
"verb": SUBMISSION_CREATED,
205+
}
206+
request = self.factory.post("/messaging", view_data)
207+
request.user = self.user
208+
209+
serializer = MessageSerializer(data=view_data, context={"request": request})
210+
self.assertTrue(serializer.is_valid())
211+
serializer.save()
212+
213+
# Verify that a new action was created (not folded)
214+
self.assertEqual(
215+
Action.objects.filter(
216+
target_content_type=self.xform_content_type,
217+
target_object_id=self.xform.pk,
218+
).count(),
219+
2,
220+
)

0 commit comments

Comments
 (0)