Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional message verbs to messaging endpoint #2321

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
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
46 changes: 43 additions & 3 deletions onadata/apps/api/tests/viewsets/test_project_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
XFormVersion,
)
from onadata.apps.main.models import MetaData
from onadata.apps.messaging.constants import (
XFORM, PROJECT, USER, PROJECT_CREATED,
FORM_CREATED, PROJECT_DELETED
)
from onadata.libs import permissions as role
from onadata.libs.models.share_project import ShareProject
from onadata.libs.permissions import (
Expand Down Expand Up @@ -108,8 +112,10 @@ def tearDown(self):
super().tearDown()

# pylint: disable=invalid-name
@patch("onadata.apps.api.viewsets.project_viewset.send_message")
@patch("onadata.apps.main.forms.requests")
def test_publish_xlsform_using_url_upload(self, mock_requests):
def test_publish_xlsform_using_url_upload(
self, mock_requests, mock_send_message):
with HTTMock(enketo_mock):
self._project_create()
view = ProjectViewSet.as_view({"post": "forms"})
Expand Down Expand Up @@ -137,6 +143,7 @@ def test_publish_xlsform_using_url_upload(self, mock_requests):
post_data = {"xls_url": xls_url}
request = self.factory.post("/", data=post_data, **self.extra)
response = view(request, pk=project_id)
xform = self.user.xforms.get(id_string="transportation_2015_01_07")

mock_requests.get.assert_called_with(xls_url)
xls_file.close()
Expand All @@ -149,6 +156,16 @@ def test_publish_xlsform_using_url_upload(self, mock_requests):
1,
)

# send message upon form deletion
self.assertTrue(mock_send_message.called)
mock_send_message.assert_called_with(
instance_id=xform.id,
target_id=xform.id,
target_type=XFORM,
user=request.user,
message_verb=FORM_CREATED
)

@override_settings(TIME_ZONE="UTC")
def test_projects_list(self):
self._publish_xls_form_to_project()
Expand Down Expand Up @@ -499,7 +516,8 @@ def test_projects_tags(self):
self.assertEqual(response.data, [])
self.assertEqual(get_latest_tags(self.project), [])

def test_projects_create(self):
@patch("onadata.apps.api.viewsets.project_viewset.send_message")
def test_projects_create(self, mock_send_message):
self._project_create()
self.assertIsNotNone(self.project_data)

Expand All @@ -510,6 +528,16 @@ def test_projects_create(self):
self.assertEqual(self.user, project.created_by)
self.assertEqual(self.user, project.organization)

# send message upon project creation
self.assertTrue(mock_send_message.called)
mock_send_message.assert_called_with(
instance_id=self.project.id,
target_id=self.project.id,
target_type=PROJECT,
user=self.user,
message_verb=PROJECT_CREATED
)

def test_project_create_other_account(self): # pylint: disable=invalid-name
"""
Test that a user cannot create a project in a different user account
Expand Down Expand Up @@ -2611,7 +2639,8 @@ def test_project_list_by_owner(self):
users,
)

def test_projects_soft_delete(self):
@patch("onadata.apps.api.viewsets.project_viewset.send_message")
def test_projects_soft_delete(self, mock_send_message):
self._project_create()

view = ProjectViewSet.as_view({"get": "list", "delete": "destroy"})
Expand All @@ -2632,6 +2661,17 @@ def test_projects_soft_delete(self):
request = self.factory.delete("/", **self.extra)
request.user = self.user
response = view(request, pk=project_id)

# send message upon project creation
self.assertTrue(mock_send_message.called)
mock_send_message.assert_called_with(
instance_id=self.project.id,
target_id=self.user.id,
target_type=USER,
user=self.user,
message_verb=PROJECT_DELETED
)

self.assertEqual(response.status_code, 204)

self.project = Project.objects.get(pk=project_id)
Expand Down
67 changes: 63 additions & 4 deletions onadata/apps/api/tests/viewsets/test_xform_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@
from onadata.apps.logger.views import delete_xform
from onadata.apps.logger.xform_instance_parser import XLSFormError
from onadata.apps.main.models import MetaData
from onadata.apps.messaging.constants import FORM_UPDATED, XFORM
from onadata.apps.messaging.constants import (
FORM_UPDATED, XFORM, PROJECT, FORM_DELETED, FORM_CREATED,
FORM_RENAMED, FORM_ACTIVE
)
from onadata.apps.viewer.models import Export
from onadata.libs.permissions import (
ROLES_ORDERED,
Expand Down Expand Up @@ -1915,7 +1918,8 @@ def test_return_400_on_xlsform_error_on_list_action(self, mock_set_title):
self.assertEqual(response.status_code, 400)
self.assertEqual(response.content.decode("utf-8"), error_msg)

def test_partial_update(self):
@patch("onadata.apps.api.viewsets.xform_viewset.send_message")
def test_partial_update(self, mock_send_message):
with HTTMock(enketo_mock):
self._publish_xls_form_to_project()
view = XFormViewSet.as_view({"patch": "partial_update"})
Expand All @@ -1939,6 +1943,38 @@ def test_partial_update(self):
request = self.factory.patch("/", data=data, **self.extra)
response = view(request, pk=self.xform.id)
self.assertEqual(response.status_code, 200)

# send messages upon form update
self.assertTrue(mock_send_message.called)

# check calls to send_message triggered by patch request
mock_calls = mock_send_message.call_args_list
args, kwargs = mock_calls[0]
# test form rename message
message_kwargs = {
"instance_id": self.xform.id,
"target_id": self.xform.id,
"target_type": XFORM,
"user": self.xform.user,
"message_verb": FORM_RENAMED,
"custom_message": {
"old_title": "transportation_2011_07_25",
"new_title": "Hello & World!"
}
}
self.assertEqual(kwargs, message_kwargs)

# test form status message
args, kwargs = mock_calls[1]
message_kwargs = {
"instance_id": self.xform.id,
"target_id": self.xform.id,
"target_type": XFORM,
"user": self.xform.user,
"message_verb": FORM_ACTIVE,
}
self.assertEqual(kwargs, message_kwargs)

xform_old_hash = self.xform.hash
self.xform.refresh_from_db()
self.assertTrue(self.xform.downloadable)
Expand Down Expand Up @@ -2074,7 +2110,8 @@ def test_form_add_project_cache(self):
cache.get(f"{PROJ_FORMS_CACHE}{self.project.pk}"), cleared_cache_content
)

def test_form_delete(self):
@patch("onadata.apps.api.viewsets.xform_viewset.send_message")
def test_form_delete(self, mock_send_message):
with HTTMock(enketo_mock):
self._publish_xls_form_to_project()

Expand All @@ -2094,6 +2131,17 @@ def test_form_delete(self):
formid = self.xform.pk
request = self.factory.delete("/", **self.extra)
response = view(request, pk=formid)

# send message upon form deletion
self.assertTrue(mock_send_message.called)
mock_send_message.assert_called_with(
instance_id=self.xform.id,
target_id=self.xform.project.pk,
target_type=PROJECT,
user=request.user,
message_verb=FORM_DELETED
)

self.assertEqual(response.data, None)
self.assertEqual(response.status_code, 204)

Expand Down Expand Up @@ -3624,8 +3672,9 @@ def test_survey_preview_endpoint(self):
self.assertEqual(response.data.get("detail"), error_message)

@override_settings(CELERY_TASK_ALWAYS_EAGER=True)
@patch("onadata.apps.api.viewsets.xform_viewset.send_message")
@patch("onadata.apps.api.tasks.get_async_status")
def test_delete_xform_async(self, mock_get_status):
def test_delete_xform_async(self, mock_get_status, mock_send_msg):
with HTTMock(enketo_mock):
mock_get_status.return_value = {"job_status": "PENDING"}
self._publish_xls_form_to_project()
Expand Down Expand Up @@ -3654,6 +3703,16 @@ def test_delete_xform_async(self, mock_get_status):
self.assertEqual(response.status_code, 202)
self.assertEqual(response.data, {"job_status": "PENDING"})

# send message upon form deletion
self.assertTrue(mock_send_msg.called)
mock_send_msg.assert_called_with(
instance_id=self.xform.id,
target_id=self.xform.project.pk,
target_type=XFORM,
user=request.user,
message_verb=FORM_DELETED
)

xform = XForm.objects.get(pk=formid)

self.assertIsNotNone(xform.deleted_at)
Expand Down
18 changes: 18 additions & 0 deletions onadata/apps/api/viewsets/export_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
"""
import os

from rest_framework import status
from rest_framework.mixins import DestroyModelMixin
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.viewsets import ReadOnlyModelViewSet

from onadata.apps.api.permissions import ExportDjangoObjectPermission
from onadata.apps.messaging.constants import EXPORT_DELETED, XFORM
from onadata.apps.messaging.serializers import send_message
from onadata.apps.viewer.models.export import Export
from onadata.libs import filters
from onadata.libs.authentication import TempTokenURLParameterAuthentication
Expand Down Expand Up @@ -57,3 +61,17 @@ def retrieve(self, request, *args, **kwargs):
file_path=export.filepath,
show_date=False,
)

def destroy(self, request, *args, **kwargs):
"""Deletes Export Object"""
export = self.get_object()
export_id = export.id
export.delete()
send_message(
instance_id=export_id,
target_id=export.xform.id,
target_type=XFORM,
user=request.user,
message_verb=EXPORT_DELETED,
)
return Response(status=status.HTTP_204_NO_CONTENT)
81 changes: 72 additions & 9 deletions onadata/apps/api/viewsets/project_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
from onadata.apps.logger.models import Project, XForm, ProjectInvitation
from onadata.apps.main.models import UserProfile
from onadata.apps.main.models.meta_data import MetaData
from onadata.apps.messaging.constants import (
USER, PROJECT, PROJECT_EDITED, XFORM, FORM_CREATED,
USER_ADDED_TO_PROJECT, USER_REMOVED_FROM_PROJECT, PROJECT_DELETED,
PROJECT_CREATED
)
from onadata.apps.messaging.serializers import send_message
from onadata.libs.data import strtobool
from onadata.libs.filters import AnonUserProjectFilter, ProjectOwnerFilter, TagFilter
from onadata.libs.mixins.authenticate_header_mixin import AuthenticateHeaderMixin
Expand Down Expand Up @@ -102,11 +108,39 @@ def get_queryset(self):

return super().get_queryset()

def create(self, request, *args, **kwargs):
"""Creates new project"""
response = super().create(request, *args, **kwargs)
project = response.data
project_id = project.get("projectid")
cache.set(f"{PROJ_OWNER_CACHE}{project_id}", response.data)

# send notification upon creating new project
send_message(
instance_id=project_id,
target_id=project_id,
target_type=PROJECT,
user=request.user,
message_verb=PROJECT_CREATED,
)

return response

def update(self, request, *args, **kwargs):
"""Updates project properties and set's cache with the updated records."""
project_id = kwargs.get("pk")
response = super().update(request, *args, **kwargs)
cache.set(f"{PROJ_OWNER_CACHE}{project_id}", response.data)

# send notification upon updating project details
send_message(
instance_id=project_id,
target_id=project_id,
target_type=PROJECT,
user=request.user,
message_verb=PROJECT_EDITED,
)

return response

def retrieve(self, request, *args, **kwargs):
Expand Down Expand Up @@ -157,6 +191,14 @@ def forms(self, request, **kwargs):
propagate_project_permissions_async.apply_async(
args=[project.id], countdown=30
)
# send form publish notification
send_message(
instance_id=survey.id,
target_id=survey.id,
target_type=XFORM,
user=request.user,
message_verb=FORM_CREATED,
)

return Response(serializer.data, status=status.HTTP_201_CREATED)

Expand All @@ -181,26 +223,30 @@ def share(self, request, *args, **kwargs):
remove = strtobool(remove)

if remove:
serializer = RemoveUserFromProjectSerializer(data={**data, remove: True})
serializer = RemoveUserFromProjectSerializer(data=data)
message_verb = USER_REMOVED_FROM_PROJECT
else:
serializer = ShareProjectSerializer(data=data)
message_verb = USER_ADDED_TO_PROJECT
if serializer.is_valid():
serializer.save()
email_msg = data.get("email_msg")
if email_msg:
# send out email message.
try:
user = serializer.instance.user
except AttributeError:
for instance in serializer.instance:
user = instance.user
try:
user = serializer.instance.user
except AttributeError:
for instance in serializer.instance:
user = instance.user
if email_msg:
# send email if email_msg is present in payload
send_mail(
SHARE_PROJECT_SUBJECT.format(self.object.name),
email_msg,
DEFAULT_FROM_EMAIL,
(user.email,),
)
else:
else:
if email_msg:
# send email if email_msg is present in payload
send_mail(
SHARE_PROJECT_SUBJECT.format(self.object.name),
email_msg,
Expand All @@ -212,6 +258,14 @@ def share(self, request, *args, **kwargs):

# clear cache
safe_delete(f"{PROJ_OWNER_CACHE}{self.object.pk}")
# send message upon sharing/unsharing project with user
send_message(
instance_id=self.object.pk,
target_id=self.object.pk,
target_type=PROJECT,
user=user,
message_verb=message_verb,
)

return Response(status=status.HTTP_204_NO_CONTENT)

Expand Down Expand Up @@ -313,4 +367,13 @@ def destroy(self, request, *args, **kwargs):
user = request.user
project.soft_delete(user)

# send notification to user target upon project deletion
send_message(
instance_id=project.pk,
target_id=user.id,
target_type=USER,
user=user,
message_verb=PROJECT_DELETED,
)

return Response(status=status.HTTP_204_NO_CONTENT)
Loading