diff --git a/ureport/api/serializers.py b/ureport/api/serializers.py index ae22bdfe2..a7dbfb4d7 100644 --- a/ureport/api/serializers.py +++ b/ureport/api/serializers.py @@ -105,6 +105,29 @@ def get_images(self, obj): for image in obj.get_featured_images() ] + # Function to use ?fields and ?exclude API calls for specific attributes in stories + def __init__(self, *args, **kwargs): + # Don't pass the 'fields' arg up to the superclass + request = kwargs.get("context", {}).get("request") + str_exclude_fields = request.GET.get("exclude", "") if request else None + str_fields = request.GET.get("fields", "") if request else None + fields = str_fields.split(",") if str_fields else None + exclude_fields = str_exclude_fields.split(",") if str_exclude_fields else None + + # Instantiate the superclass normally + super(StoryReadSerializer, self).__init__(*args, **kwargs) + + if exclude_fields is not None: + # Drop any fields that are specified in the `exclude` argument. + exclude_allowed = set(exclude_fields) + for field_name in exclude_allowed: + self.fields.pop(field_name) + elif fields is not None: + allowed_fields = set(fields) + existing_data = set(self.fields) + for field_names in existing_data - allowed_fields: + self.fields.pop(field_names) + class Meta: model = Story fields = ( @@ -127,6 +150,29 @@ class PollReadSerializer(serializers.ModelSerializer): category = CategoryReadSerializer() questions = SerializerMethodField() + # Function to use ?fields and ?exclude API calls for specific attributes in polls + def __init__(self, *args, **kwargs): + # Don't pass the 'fields' arg up to the superclass + request = kwargs.get("context", {}).get("request") + str_exclude_fields = request.GET.get("exclude", "") if request else None + str_fields = request.GET.get("fields", "") if request else None + fields = str_fields.split(",") if str_fields else None + exclude_fields = str_exclude_fields.split(",") if str_exclude_fields else None + + # Instantiate the superclass normally + super(PollReadSerializer, self).__init__(*args, **kwargs) + + if exclude_fields is not None: + # Drop any fields that are specified in the `exclude` argument. + exclude_allowed = set(exclude_fields) + for field_name in exclude_allowed: + self.fields.pop(field_name) + elif fields is not None: + allowed_fields = set(fields) + existing_data = set(self.fields) + for field_names in existing_data - allowed_fields: + self.fields.pop(field_names) + def get_questions(self, obj): questions = [] for question in obj.get_questions(): diff --git a/ureport/api/tests.py b/ureport/api/tests.py index 14e6e5698..fe59773ea 100644 --- a/ureport/api/tests.py +++ b/ureport/api/tests.py @@ -398,6 +398,40 @@ def test_polls_by_org_list_with_flow_uuid_parameter(self): self.assertEqual(response.data["count"], 1) self.assertEqual(response.data["results"][0]["title"], "registration") + def test_polls_by_org_list_with_fields_parameter(self): + url = "/api/v1/polls/org/%d/?fields=%s" % (self.uganda.pk, "title") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + count_polls = Poll.objects.filter(org=self.uganda, is_active=True, has_synced=True).count() + self.assertEqual(response.data["count"], count_polls) + polls = [self.second_featured_poll, self.first_featured_poll, self.another_poll, self.reg_poll] + for i in range(count_polls): + self.assertEqual(response.data["results"][i]["title"], polls[i].title) + + def test_polls_by_org_list_with_exclude_parameter(self): + url = "/api/v1/polls/org/%d/?exclude=%s,%s,%s,%s,%s,%s" % ( + self.uganda.pk, + "flow_uuid", + "title", + "category", + "poll_date", + "modified_on", + "created_on", + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + count_polls = Poll.objects.filter(org=self.uganda, is_active=True, has_synced=True).count() + poll = self.reg_poll + self.assertEqual(response.data["count"], count_polls) + self.assertDictEqual( + response.data["results"][3], + dict( + id=poll.pk, + org=poll.org_id, + questions=[], + ), + ) + def test_featured_poll_by_org_list_when_featured_polls_exists(self): url = "/api/v1/polls/org/%d/featured/" % self.uganda.pk response = self.client.get(url) @@ -412,6 +446,13 @@ def test_featured_poll_by_org_list_when_featured_polls_exists(self): self.assertEqual(response.data["count"], 2) self.assertTrue(response.data["results"][0]["modified_on"] > response.data["results"][1]["modified_on"]) + def test_featured_poll_by_org_list_with_fields_parameter_when_featured_polls_exists(self): + url = "/api/v1/polls/org/%d/featured/?fields=%s" % (self.uganda.pk, "created_on") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["count"], 2) + self.assertTrue(response.data["results"][0]["created_on"] > response.data["results"][1]["created_on"]) + def test_featured_poll_by_org_list_when_no_featured_polls_exists(self): url = "/api/v1/polls/org/%d/featured/" % self.nigeria.pk response = self.client.get(url) @@ -502,6 +543,112 @@ def test_single_poll(self): ), ) + def test_single_poll_with_fields_parameter(self): + url = "/api/v1/polls/%d/?fields=%s,%s,%s" % (self.reg_poll.pk, "id", "title", "questions") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + poll = self.reg_poll + self.assertDictEqual( + response.data, + dict( + id=poll.pk, + title=poll.title, + questions=[], + ), + ) + + with patch("ureport.polls.models.PollQuestion.get_results") as mock_get_results: + mock_get_results.return_value = [dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT")] + + poll_question = self.create_poll_question(self.superuser, self.reg_poll, "What's on mind? :)", "uuid1") + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( + response.data, + dict( + id=poll.pk, + title=poll.title, + questions=[ + dict( + id=poll_question.pk, + ruleset_uuid="uuid1", + title="What's on mind? :)", + results=dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT"), + results_by_age=[dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT")], + results_by_gender=[dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT")], + results_by_location=[ + dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT") + ], + ) + ], + ), + ) + + poll_question.is_active = False + poll_question.save() + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( + response.data, + dict( + id=poll.pk, + title=poll.title, + questions=[], + ), + ) + + def test_single_poll_with_exclude_parameter(self): + url = "/api/v1/polls/%d/?exclude=%s,%s,%s,%s" % (self.reg_poll.pk, "id", "title", "category", "questions") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + poll = self.reg_poll + self.assertDictEqual( + response.data, + dict( + flow_uuid=poll.flow_uuid, + org=poll.org_id, + poll_date=poll.poll_date.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + modified_on=poll.modified_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + created_on=poll.created_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + ), + ) + + with patch("ureport.polls.models.PollQuestion.get_results") as mock_get_results: + mock_get_results.return_value = [dict(set=20, unset=10, open_ended=False, categories="CATEGORIES-DICT")] + + poll_question = self.create_poll_question(self.superuser, self.reg_poll, "What's on mind? :)", "uuid1") + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( + response.data, + dict( + flow_uuid=poll.flow_uuid, + org=poll.org_id, + created_on=poll.created_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + modified_on=poll.modified_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + poll_date=poll.poll_date.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + ), + ) + + poll_question.is_active = False + poll_question.save() + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual( + response.data, + dict( + flow_uuid=poll.flow_uuid, + org=poll.org_id, + poll_date=poll.poll_date.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + modified_on=poll.modified_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + created_on=poll.created_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + ), + ) + def test_news_item_by_org_list(self): url = "/api/v1/news/org/%d/" % self.uganda.pk url1 = "/api/v1/news/org/%d/" % self.nigeria.pk @@ -604,6 +751,43 @@ def test_single_story(self): dict(name=story.category.name, image_url=CategoryReadSerializer().get_image_url(story.category)), ) + def test_single_story_with_fields_parameter(self): + url = "/api/v1/stories/%d/?fields=%s" % (self.uganda_story.pk, "content") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + story = self.uganda_story + self.assertDictEqual( + response.data, + dict( + content=story.content, + ), + ) + + def test_single_story_with_exclude_parameter(self): + url = "/api/v1/stories/%d/?exclude=%s,%s,%s,%s" % ( + self.uganda_story.pk, + "content", + "featured", + "images", + "category", + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + story = self.uganda_story + self.assertDictEqual( + response.data, + dict( + id=story.pk, + title=story.title, + video_id=story.video_id, + audio_link=story.audio_link, + summary=story.summary, + tags=story.tags, + org=story.org_id, + created_on=story.created_on.strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + ), + ) + def test_dashblock_by_org_list(self): url_uganda = "/api/v1/dashblocks/org/%d/" % self.uganda.pk url_nigeria = "/api/v1/dashblocks/org/%d/" % self.nigeria.pk diff --git a/ureport/api/views.py b/ureport/api/views.py index c8d48f495..7dae0a838 100644 --- a/ureport/api/views.py +++ b/ureport/api/views.py @@ -262,6 +262,26 @@ class PollList(BaseListAPIView): ... ] } + + + If you want to get polls with only specific attributes: + + Example: + + GET /api/v1/polls/org/{org}/?fields=title,flow_uuid + + Response is polls with only title and flow_uuid attributes. + + + If you want to get polls without specific attributes: + + Example: + + GET /api/v1/polls/org/{org}/?exclude=title,flow_uuid + + Response is polls without title and flow_uuid attributes. + + """ serializer_class = PollReadSerializer @@ -395,6 +415,26 @@ class PollDetails(RetrieveAPIView): }, "created_on": "2015-09-02T08:53:30.313251Z" } + + + If you want to get a poll with only specific attributes: + + Example: + + GET /api/v1/polls/{id}/?fields=title,flow_uuid + + Response is a poll with only title and flow_uuid attributes. + + + If you want to get a poll without specific attributes: + + Example: + + GET /api/v1/polls/{id}/?exclude=title,flow_uuid + + Response is a poll without title and flow_uuid attributes. + + """ serializer_class = PollReadSerializer @@ -532,6 +572,25 @@ class FeaturedPollList(BaseListAPIView): ... ] } + + If you want to get the featured poll with only specific attributes: + + Example: + + GET /api/v1/polls/org/{org}/featured/?fields=title,flow_uuid + + Response is the featured poll with only title and flow_uuid attributes. + + + If you want to get the featured poll without specific attributes: + + Example: + + GET /api/v1/polls/org/{org}/featured/?exclude=title,flow_uuid + + Response is the featured poll without title and flow_uuid attributes. + + """ serializer_class = PollReadSerializer @@ -829,6 +888,26 @@ class StoryList(BaseListAPIView): }, ... } + + + If you want to get stories with only specific attributes: + + Example: + + GET /api/v1/stories/org/{org}/?fields=title,content + + Response is stories with only title and content attributes. + + + If you want to get stories without specific attributes: + + Example: + + GET /api/v1/stories/org/{org}/?exclude=title,content + + Response is stories without title and content attributes. + + """ serializer_class = StoryReadSerializer @@ -866,6 +945,25 @@ class StoryDetails(RetrieveAPIView): "name": "tests" } } + + If you want to get a story with only specific attributes: + + Example: + + GET /api/v1/stories/{id}/?fields=title,content + + Response is a story with only title and content attributes. + + + If you want to get a story without specific attributes: + + Example: + + GET /api/v1/stories/{id}/?exclude=title,content + + Response is a story without title and content attributes. + + """ serializer_class = StoryReadSerializer