From 8bd0a1150f83b702e7cbbcd732baf60ccf9ec289 Mon Sep 17 00:00:00 2001 From: Davis Raymond Muro Date: Mon, 13 Jan 2020 15:20:44 +0300 Subject: [PATCH 1/2] Update data endpoint documentation - Add information about '$or' filter option --- docs/data.rst | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/data.rst b/docs/data.rst index de75818087..e6443bfbae 100644 --- a/docs/data.rst +++ b/docs/data.rst @@ -431,18 +431,26 @@ Query submissions with case insensitive and partial search curl -X GET https://api.ona.io/api/v1/data/22845?query={"name":{"$i":"hosee"}} +Example V +^^^^^^^ +Query submissions where age is 21 or name is hosee + +:: + + curl -X GET https://api.ona.io/api/v1/data/22845?query={"$or": [{"age": "21", "name": "hosee"}]} All Filters Options -======== =================================== -Filter Description -======== =================================== -**$gt** Greater than -**$gte** Greater than or Equal to -**$lt** Less than -**$lte** Less or Equal to -**$i** Case insensitive or partial search -======== =================================== +======= =================================== +Filter Description +======= =================================== +**$gt** Greater than +**$gte** Greater than or Equal to +**$lt** Less than +**$lte** Less or Equal to +**$i** Case insensitive or partial search +**$or** Or +======= =================================== Query submitted data of a specific form using date_created ---------------------------------------------------------- From 33a819a3b4cf92066687360690f78a102752f2b8 Mon Sep 17 00:00:00 2001 From: Davis Raymond Muro Date: Wed, 15 Jan 2020 12:44:08 +0300 Subject: [PATCH 2/2] Modify $or filter option to accept null values - Update test --- .../api/tests/viewsets/test_data_viewset.py | 85 ++++++++++++++++++- onadata/apps/viewer/parsed_instance_tools.py | 11 ++- 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/onadata/apps/api/tests/viewsets/test_data_viewset.py b/onadata/apps/api/tests/viewsets/test_data_viewset.py index ef71918f8e..642292932d 100644 --- a/onadata/apps/api/tests/viewsets/test_data_viewset.py +++ b/onadata/apps/api/tests/viewsets/test_data_viewset.py @@ -24,9 +24,8 @@ from onadata.apps.api.viewsets.data_viewset import DataViewSet from onadata.apps.api.viewsets.project_viewset import ProjectViewSet from onadata.apps.api.viewsets.xform_viewset import XFormViewSet -from onadata.apps.logger.models import Attachment -from onadata.apps.logger.models import Instance, SurveyType -from onadata.apps.logger.models import XForm +from onadata.apps.logger.models import \ + Instance, SurveyType, XForm, Attachment, SubmissionReview from onadata.apps.logger.models.instance import InstanceHistory from onadata.apps.logger.models.instance import get_attachment_url from onadata.apps.main import tests as main_tests @@ -38,6 +37,8 @@ EditorMinorRole, DataEntryOnlyRole, DataEntryMinorRole from onadata.libs.utils.common_tags import MONGO_STRFTIME from onadata.libs.utils.logger_tools import create_instance +from onadata.libs.serializers.submission_review_serializer import \ + SubmissionReviewSerializer @urlmatch(netloc=r'(.*\.)?enketo\.ona\.io$') @@ -2234,6 +2235,84 @@ def test_floip_format(self): 'transport/available_transportation_types_to_referral_facility') self.assertEqual(floip_row[5], 'none') + def test_data_query_ornull(self): + """ + Test that a user is able to query for null with the + $or filter option + """ + self._make_submissions() + view = DataViewSet.as_view({'get': 'list'}) + request = self.factory.get('/', **self.extra) + formid = self.xform.pk + response = view(request, pk=formid) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 4) + + query_str = ('{"$or": [{"_review_status":"3"}' + ', {"_review_status": null }]}') + request = self.factory.get('/?query=%s' % query_str, **self.extra) + response = view(request, pk=formid) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 4) + + instances = self.xform.instances.all().order_by('pk') + instance = instances[0] + self.assertFalse(instance.has_a_review) + + # Review instance + data = { + "instance": instance.id, + "status": SubmissionReview.APPROVED + } + + serializer_instance = SubmissionReviewSerializer(data=data) + serializer_instance.is_valid() + serializer_instance.save() + instance.refresh_from_db() + + # Assert that the approved instance is no longer returned + # when querying + response = view(request, pk=formid) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 3) + + # Switch review to pending + data = { + "instance": instance.id, + "status": SubmissionReview.PENDING + } + + serializer_instance = SubmissionReviewSerializer(data=data) + serializer_instance.is_valid() + serializer_instance.save() + instance.refresh_from_db() + + # Assert instance is now a part of the values when querying + response = view(request, pk=formid) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 4) + + instance = instances[1] + # Switch review to Rejected + data = { + "instance": instance.id, + "status": SubmissionReview.REJECTED, + "note": "Testing" + } + + serializer_instance = SubmissionReviewSerializer(data=data) + serializer_instance.is_valid() + serializer_instance.save() + instance.refresh_from_db() + + # Assert ornull operator still works with multiple values + query_str = ('{"$or": [{"_review_status":"3"},' + ' {"_review_status": "2"}, {"_review_status": null}]}') + request = self.factory.get('/?query=%s' % query_str, **self.extra) + response = view(request, pk=formid) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 4) + class TestOSM(TestAbstractViewSet): """ diff --git a/onadata/apps/viewer/parsed_instance_tools.py b/onadata/apps/viewer/parsed_instance_tools.py index b1c147be10..274c358c98 100644 --- a/onadata/apps/viewer/parsed_instance_tools.py +++ b/onadata/apps/viewer/parsed_instance_tools.py @@ -103,10 +103,15 @@ def get_where_clause(query, form_integer_fields=None, if isinstance(query, dict) and '$or' in list(query): or_dict = query.pop('$or') + for l in or_dict: - or_where.extend([u"json->>%s = %s" for i in iteritems(l)]) - for i in iteritems(l): - or_params.extend(i) + for k, v in l.items(): + if v is None: + or_where.extend([u"json->>'{}' IS NULL".format(k)]) + else: + or_where.extend( + [u"json->>%s = %s"]) + or_params.extend([k, v]) or_where = [u"".join([u"(", u" OR ".join(or_where), u")"])]