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 support for RapidPro JSON webhook post requests #1807

Merged
merged 2 commits into from
Apr 9, 2020
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
37 changes: 37 additions & 0 deletions onadata/apps/api/tests/viewsets/test_xform_submission_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,43 @@ def test_rapidpro_post_submission(self):
Test a Rapidpro Webhook POST submission.
"""
# pylint: disable=C0301
data = json.dumps({
'contact': {
'name': 'Davis',
'urn': 'tel:+12065551212',
'uuid': '23dae14f-7202-4ff5-bdb6-2390d2769968'
},
'flow': {
'name': '1166',
'uuid': '9da5e439-35af-4ecb-b7fc-2911659f6b04'
},
'results': {
'fruit_name': {
'category': 'All Responses',
'value': 'orange'
},
}
})

request = self.factory.post(
'/submission', data,
content_type='application/json',
HTTP_USER_AGENT='RapidProMailroom/5.2.0')
response = self.view(request)
self.assertEqual(response.status_code, 401)
auth = DigestAuth('bob', 'bobbob')
request.META.update(auth(request.META, response))
response = self.view(request, username=self.user.username,
xform_pk=self.xform.pk)
self.assertContains(response, 'Successful submission', status_code=201)
self.assertTrue(response.has_header('Date'))
self.assertEqual(response['Location'], 'http://testserver/submission')

def test_legacy_rapidpro_post_submission(self):
"""
Test a Legacy Rapidpro Webhook POST submission.
"""
# pylint: disable=C0301
data = 'run=76250&text=orange&flow_uuid=9da5e439-35af-4ecb-b7fc-2911659f6b04&phone=%2B12065550100&step=3b15df81-a0bd-4de7-8186-145ea3bb6c43&contact_name=Antonate+Maritim&flow_name=fruit&header=Authorization&urn=tel%3A%2B12065550100&flow=1166&relayer=-1&contact=fe4df540-39c1-4647-b4bc-1c93833e22e0&values=%5B%7B%22category%22%3A+%7B%22base%22%3A+%22All+Responses%22%7D%2C+%22node%22%3A+%228037c12f-a277-4255-b630-6a03b035767a%22%2C+%22time%22%3A+%222017-10-04T07%3A18%3A08.171069Z%22%2C+%22text%22%3A+%22orange%22%2C+%22rule_value%22%3A+%22orange%22%2C+%22value%22%3A+%22orange%22%2C+%22label%22%3A+%22fruit_name%22%7D%5D&time=2017-10-04T07%3A18%3A08.205524Z&steps=%5B%7B%22node%22%3A+%220e18202f-9ec4-4756-b15b-e9f152122250%22%2C+%22arrived_on%22%3A+%222017-10-04T07%3A15%3A17.548657Z%22%2C+%22left_on%22%3A+%222017-10-04T07%3A15%3A17.604668Z%22%2C+%22text%22%3A+%22Fruit%3F%22%2C+%22type%22%3A+%22A%22%2C+%22value%22%3A+null%7D%2C+%7B%22node%22%3A+%228037c12f-a277-4255-b630-6a03b035767a%22%2C+%22arrived_on%22%3A+%222017-10-04T07%3A15%3A17.604668Z%22%2C+%22left_on%22%3A+%222017-10-04T07%3A18%3A08.171069Z%22%2C+%22text%22%3A+%22orange%22%2C+%22type%22%3A+%22R%22%2C+%22value%22%3A+%22orange%22%7D%2C+%7B%22node%22%3A+%223b15df81-a0bd-4de7-8186-145ea3bb6c43%22%2C+%22arrived_on%22%3A+%222017-10-04T07%3A18%3A08.171069Z%22%2C+%22left_on%22%3A+null%2C+%22text%22%3A+null%2C+%22type%22%3A+%22A%22%2C+%22value%22%3A+null%7D%5D&flow_base_language=base&channel=-1' # noqa
request = self.factory.post(
'/submission', data,
Expand Down
8 changes: 6 additions & 2 deletions onadata/apps/api/viewsets/xform_submission_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
from onadata.libs.renderers.renderers import FLOIPRenderer, TemplateXMLRenderer
from onadata.libs.serializers.data_serializer import (
FLOIPSubmissionSerializer, JSONSubmissionSerializer,
RapidProSubmissionSerializer, SubmissionSerializer)
RapidProSubmissionSerializer, SubmissionSerializer,
RapidProJSONSubmissionSerializer)
from onadata.libs.utils.logger_tools import OpenRosaResponseBadRequest

BaseViewset = get_baseviewset_class() # pylint: disable=C0103
Expand Down Expand Up @@ -83,9 +84,12 @@ def get_serializer_class(self):
content_type = self.request.content_type.lower()

if 'application/json' in content_type:
if 'RapidProMailroom' in self.request.META.get(
'HTTP_USER_AGENT', ''):
return RapidProJSONSubmissionSerializer

self.request.accepted_renderer = JSONRenderer()
self.request.accepted_media_type = 'application/json'

return JSONSubmissionSerializer

if 'application/x-www-form-urlencoded' in content_type:
Expand Down
64 changes: 43 additions & 21 deletions onadata/libs/serializers/data_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ def to_representation(self, instance):
}


class BaseRapidProSubmissionSerializer(SubmissionSuccessMixin,
serializers.Serializer):
"""
Base Rapidpro SubmissionSerializer - Implements the basic functionalities
of a Rapidpro webhook serializer
"""
def validate(self, attrs):
"""
Validate that the XForm ID is passed in view kwargs
"""
view = self.context['view']

if 'xform_pk' in view.kwargs:
xform_pk = view.kwargs.get('xform_pk')
xform = get_object_or_404(XForm, pk=xform_pk)
attrs.update({'id_string': xform.id_string})
else:
raise serializers.ValidationError({
'xform_pk':
_(u'Incorrect url format. Use format '
u'https://api.ona.io/username/formid/submission')})

return super(BaseRapidProSubmissionSerializer, self).validate(attrs)


# pylint: disable=W0223
class SubmissionSerializer(SubmissionSuccessMixin, serializers.Serializer):
"""
Expand Down Expand Up @@ -277,30 +302,10 @@ def create(self, validated_data):
return instance


class RapidProSubmissionSerializer(SubmissionSuccessMixin,
serializers.Serializer):
class RapidProSubmissionSerializer(BaseRapidProSubmissionSerializer):
"""
Rapidpro SubmissionSerializer - handles Rapidpro webhook post.
"""
def validate(self, attrs):
"""
Custom xform id validator in views kwargs.
"""
view = self.context['view']

if 'xform_pk' in view.kwargs:
xform_pk = view.kwargs.get('xform_pk')
xform = get_object_or_404(XForm, pk=xform_pk)
attrs.update({'id_string': xform.id_string})
else:
raise serializers.ValidationError({
'xform_pk':
_(u'Incorrect url format, Use format '
u'https://api.ona.io/username/formid/submission')
})

return super(RapidProSubmissionSerializer, self).validate(attrs)

def create(self, validated_data):
"""
Returns object instances based on the validated data.
Expand All @@ -313,6 +318,23 @@ def create(self, validated_data):
return instance


class RapidProJSONSubmissionSerializer(BaseRapidProSubmissionSerializer):
"""
Rapidpro SubmissionSerializer - handles RapidPro JSON webhook posts
"""
def create(self, validated_data):
"""
Returns object instances based on validated data.
"""
request, username = get_request_and_username(self.context)
post_data = request.data.get('results')
instance_data_dict = {
k: post_data[k].get('value') for k in post_data.keys()}
instance = create_submission(
request, username, instance_data_dict, validated_data['id_string'])
return instance


class FLOIPListSerializer(serializers.ListSerializer):
"""
Custom ListSerializer for a FLOIP submission.
Expand Down