diff --git a/onadata/apps/api/tests/viewsets/test_xform_submission_viewset.py b/onadata/apps/api/tests/viewsets/test_xform_submission_viewset.py index c4740a388a..3e3b711ec2 100644 --- a/onadata/apps/api/tests/viewsets/test_xform_submission_viewset.py +++ b/onadata/apps/api/tests/viewsets/test_xform_submission_viewset.py @@ -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, diff --git a/onadata/apps/api/viewsets/xform_submission_viewset.py b/onadata/apps/api/viewsets/xform_submission_viewset.py index fae8936e25..a5a9371601 100644 --- a/onadata/apps/api/viewsets/xform_submission_viewset.py +++ b/onadata/apps/api/viewsets/xform_submission_viewset.py @@ -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 @@ -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: diff --git a/onadata/libs/serializers/data_serializer.py b/onadata/libs/serializers/data_serializer.py index 21e59573a6..4bdfac3cc0 100644 --- a/onadata/libs/serializers/data_serializer.py +++ b/onadata/libs/serializers/data_serializer.py @@ -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): """ @@ -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. @@ -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.