Skip to content
Open
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
39 changes: 38 additions & 1 deletion onadata/apps/messaging/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from actstream.signals import action
from rest_framework import exceptions, serializers

from onadata.apps.messaging.constants import MESSAGE, MESSAGE_VERBS
from onadata.apps.messaging.constants import MESSAGE, MESSAGE_VERBS, SUBMISSION_CREATED
from onadata.apps.messaging.utils import TargetDoesNotExist, get_target
from onadata.libs.utils.common_tools import report_exception

Expand Down Expand Up @@ -78,6 +78,7 @@ def __init__(self, *args, **kwargs):
for field in extra_fields:
self.fields.pop(field)

# pylint: disable=too-many-locals
def create(self, validated_data):
"""
Creates the Message in the Action model
Expand Down Expand Up @@ -108,6 +109,42 @@ def create(self, validated_data):
% target_object
)
raise exceptions.PermissionDenied(detail=message)

# Check if we should fold this message into an existing action
a = Action.objects.filter(
target_content_type=content_type, target_object_id=target_id
).first()
if a is not None and a.verb == SUBMISSION_CREATED:
a_json = json.loads(a.description)
description = a_json.get("description")
a_ids = a_json.get("id", [])
message_id_limit = getattr(settings, "NOTIFICATION_ID_LIMIT", 100)

# Parse the incoming description to check if it's also imported_via_csv
description_data = validated_data.get("description")
if isinstance(description_data, str):
description_data = json.loads(description_data)
incoming_description = (
description_data.get("description")
if isinstance(description_data, dict)
else None
)

if (
description == "imported_via_csv"
and incoming_description == "imported_via_csv"
and len(a_ids) < message_id_limit
):
submission_id = description_data.get("id")

# Add the new submission ID to the existing list
if submission_id is not None:
a_ids.append(submission_id)
a_json["id"] = a_ids
a.description = json.dumps(a_json)
a.save()
return a

results = action.send(
request.user,
verb=verb,
Expand Down
120 changes: 119 additions & 1 deletion onadata/apps/messaging/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
Tests for messaging serializers.
"""

import json
from unittest.mock import MagicMock, patch

from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase

from actstream.models import Action
from rest_framework.test import APIRequestFactory

from onadata.apps.main.tests.test_base import TestBase
from onadata.apps.messaging.constants import EXPORT_CREATED, SUBMISSION_CREATED, XFORM
from onadata.apps.messaging.serializers import send_message
from onadata.apps.messaging.serializers import MessageSerializer, send_message

User = get_user_model()

Expand Down Expand Up @@ -100,3 +106,115 @@ def test_send_message_with_list_of_ids(self):
call_args = mock_serializer.call_args
self.assertEqual(call_args[1]["data"]["message"], '{"id": [1, 2, 3]}')
mock_instance.save.assert_called_once()


class TestMessageFolding(TestBase):
"""
Test message folding functionality for imported_via_csv submissions.
"""

def setUp(self):
super().setUp()
self.factory = APIRequestFactory()
self._create_user_and_login()
self._publish_transportation_form()
self.xform_content_type = ContentType.objects.get_for_model(self.xform)

def test_message_folding_when_ids_below_limit(self):
"""
Test that messages are folded into an existing action when:
- An existing Action exists for the XForm
- The action's verb is SUBMISSION_CREATED
- The description is 'imported_via_csv'
- The number of IDs is less than 100 (NOTIFICATION_ID_LIMIT)
"""
# Create an initial action with the right conditions (50 IDs, below limit)
ids_list = list(range(1, 51))
initial_description = json.dumps(
{"id": ids_list, "description": "imported_via_csv"}
)
initial_action = Action.objects.create(
actor_content_type=self.xform_content_type,
actor_object_id=self.xform.pk,
verb=SUBMISSION_CREATED,
target_content_type=self.xform_content_type,
target_object_id=self.xform.pk,
description=initial_description,
)

# Create a new message that should be folded into the existing one
view_data = {
"message": json.dumps({"id": 51, "description": "imported_via_csv"}),
"target_id": self.xform.pk,
"target_type": XFORM,
"verb": SUBMISSION_CREATED,
}
request = self.factory.post("/messaging", view_data)
request.user = self.user

serializer = MessageSerializer(data=view_data, context={"request": request})
self.assertTrue(serializer.is_valid())
result = serializer.save()

# Verify that the same action was returned (folded)
self.assertEqual(result.id, initial_action.id)

# Verify that the new submission ID was added to the existing action
updated_action = Action.objects.get(id=initial_action.id)
updated_description = json.loads(updated_action.description)
self.assertEqual(updated_description["description"], "imported_via_csv")
self.assertIn(51, updated_description["id"])
self.assertEqual(len(updated_description["id"]), 51)

# Verify that only one action exists for this xform
self.assertEqual(
Action.objects.filter(
target_content_type=self.xform_content_type,
target_object_id=self.xform.pk,
).count(),
1,
)

def test_no_folding_when_conditions_not_met(self):
"""
Test that a new action is created (no folding) when conditions are not met:
- ID count at or above limit (100)
- Wrong verb (not SUBMISSION_CREATED)
- Wrong description (not 'imported_via_csv')
- No existing action
"""
# Test 1: ID count at limit - should NOT fold
ids_list = list(range(1, 101)) # 100 IDs at the limit
initial_description = json.dumps(
{"id": ids_list, "description": "imported_via_csv"}
)
Action.objects.create(
actor_content_type=self.xform_content_type,
actor_object_id=self.xform.pk,
verb=SUBMISSION_CREATED,
target_content_type=self.xform_content_type,
target_object_id=self.xform.pk,
description=initial_description,
)

view_data = {
"message": json.dumps({"id": 101, "description": "imported_via_csv"}),
"target_id": self.xform.pk,
"target_type": XFORM,
"verb": SUBMISSION_CREATED,
}
request = self.factory.post("/messaging", view_data)
request.user = self.user

serializer = MessageSerializer(data=view_data, context={"request": request})
self.assertTrue(serializer.is_valid())
serializer.save()

# Verify that a new action was created (not folded)
self.assertEqual(
Action.objects.filter(
target_content_type=self.xform_content_type,
target_object_id=self.xform.pk,
).count(),
2,
)
Loading