Fakultas Ilmu Komputer UI

Commit d3b86dda authored by Dave Nathanael's avatar Dave Nathanael
Browse files

Merge branch 'dave/export-data-csv' into 'staging'

Implement CSV Export for `InvestigationCase`

See merge request !54
parents e01f43ab 0c17c375
Pipeline #48150 passed with stages
in 6 minutes and 48 seconds
from rest_framework_csv.renderers import CSVRenderer
INVESTIGATION_CASE_RENDERER_FIELDS = [
"id",
"case_subject__age",
"case_subject__is_male",
"case_subject__district",
"outcome",
"is_positive",
"case_relation",
"medical_symptoms",
"risk_factors",
"is_referral_needed",
"medical_facility_reference",
"created_at",
"author__name",
]
class InvestigationCaseCSVRenderer(CSVRenderer):
header = INVESTIGATION_CASE_RENDERER_FIELDS
import json
import csv
import io
from rest_framework import status
from rest_framework.test import APITestCase
......@@ -6,11 +8,10 @@ from apps.cases.models import CaseSubject
from apps.cases.tests.factories.case_subjects import CaseSubjectFactory
from apps.cases.tests.factories.cases import InvestigationCaseFactory
from apps.exportables.renderers import INVESTIGATION_CASE_RENDERER_FIELDS
class ExportableViewTest(APITestCase):
@classmethod
def setUpTestData(cls):
InvestigationCaseFactory(
def init_data():
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Beji",
age=19,
......@@ -18,38 +19,44 @@ class ExportableViewTest(APITestCase):
),
is_positive=True,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Cilodong",
age=20,
is_male=False,
),
is_positive=True,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Tapos",
age=21,
is_male=True,
),
is_positive=False,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Unknown",
age=21,
is_male=False,
),
is_positive=False,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Sawangan",
age=22,
is_male=True,
),
is_positive=None,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Cilodong",
age=20,
is_male=False,
),
is_positive=True,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Tapos",
age=21,
is_male=True,
),
is_positive=False,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Unknown",
age=21,
is_male=False,
),
is_positive=False,
)
InvestigationCaseFactory(
case_subject=CaseSubjectFactory(
district="Sawangan",
age=22,
is_male=True,
),
is_positive=None,
)
class ExportableViewTest(APITestCase):
@classmethod
def setUpTestData(cls):
init_data()
def test_exportable_data_return_values(self):
url = "/exportables/"
......@@ -176,3 +183,59 @@ class ExportableViewTest(APITestCase):
}
self.assertJSONEqual(response.content, data)
class ExportInvestigationCaseViewTest(APITestCase):
@classmethod
def setUpTestData(cls):
cls.BASE_URL = "/exportables/investigation-cases-csv/"
init_data()
def export_csv_test_util(self, filter):
url = self.BASE_URL + filter
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
content_disposition_header = response.get('Content-Disposition')
self.assertIn('attachment;', content_disposition_header)
self.assertIn('filename="cases-', content_disposition_header)
self.assertIn('.csv', content_disposition_header)
response_content = response.content.decode('utf-8')
reader = csv.reader(io.StringIO(response_content))
body = list(reader)
headers = body.pop(0)
self.assertEqual(headers, INVESTIGATION_CASE_RENDERER_FIELDS)
return body
def wrong_query_param_test_util(self, filter):
url = self.BASE_URL + filter
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_export_investigation_data_to_csv_all(self):
body = self.export_csv_test_util("")
self.assertEqual(len(body), 5)
def test_export_investigation_data_to_csv_filter_gender(self):
body = self.export_csv_test_util("?is_male=false")
self.assertEqual(len(body), 2)
def test_export_investigation_data_to_csv_filter_age(self):
body = self.export_csv_test_util("?min_age=21&max_age=21")
self.assertEqual(len(body), 2)
def test_export_investigation_data_to_csv_filter_district(self):
body = self.export_csv_test_util("?district=Beji")
self.assertEqual(len(body), 1)
def test_export_investigation_data_to_csv_wrong_gender_format(self):
self.wrong_query_param_test_util("?is_male=asdf")
def test_export_investigation_data_to_csv_wrong_min_age_format(self):
self.wrong_query_param_test_util("?min_age=abc&max_age=12")
def test_export_investigation_data_to_csv_wrong_max_age_format(self):
self.wrong_query_param_test_util("?min_age=10&max_age=asd")
from django.urls import path
from apps.exportables.views import CaseCountsView
from apps.exportables.views import CaseCountsView, ExportInvestigationCaseView
urlpatterns = [
path("", CaseCountsView.as_view()),
path("investigation-cases-csv/", ExportInvestigationCaseView.as_view()),
]
from django.http import JsonResponse
from django.db.models import Max
from rest_framework import status
from django.utils import timezone
from rest_framework import status, serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.cases.models import (
......@@ -25,6 +27,10 @@ from apps.exportables.utils import (
map_outcome_value,
map_sex_value,
)
from apps.exportables.renderers import (
InvestigationCaseCSVRenderer,
INVESTIGATION_CASE_RENDERER_FIELDS,
)
class CaseCountsView(APIView):
......@@ -33,11 +39,11 @@ class CaseCountsView(APIView):
case_subjects = CaseSubject.objects.all()
investigation_cases = (InvestigationCase.objects
.values("case_subject")
.annotate(latest_investigation_case=Max("created_at"))
.order_by()
.values("case_subject__id", "is_positive")
)
.values("case_subject")
.annotate(latest_investigation_case=Max("created_at"))
.order_by()
.values("case_subject__id", "is_positive")
)
outcomes = {}
......@@ -82,3 +88,48 @@ class CaseCountsView(APIView):
}
return JsonResponse(data=data, status=status.HTTP_200_OK)
class ExportInvestigationCaseView(APIView):
renderer_classes = (InvestigationCaseCSVRenderer,)
def get(self, request, format=None):
cases = InvestigationCase.objects.all()
query_is_male = self.request.query_params.get('is_male', None)
query_min_age = self.request.query_params.get('min_age', None)
query_max_age = self.request.query_params.get('max_age', None)
query_district = self.request.query_params.get('district', None)
if query_is_male is not None:
query_is_male = query_is_male.lower()
if query_is_male not in ['true', 'false']:
raise serializers.ValidationError(
'is_male query param must be either "true" or "false"')
cases = cases.filter(case_subject__is_male=(query_is_male == 'true'))
if (query_min_age is not None) and (query_max_age is not None):
try:
query_min_age = int(query_min_age)
except ValueError:
raise serializers.ValidationError('min_age query param must be an integer')
try:
query_max_age = int(query_max_age)
except ValueError:
raise serializers.ValidationError('max_age query param must be an integer')
cases = cases.filter(case_subject__age__range=(query_min_age, query_max_age))
if query_district is not None:
cases = cases.filter(case_subject__district=query_district)
data = list(
cases.prefetch_related(
"case_subject",
"reference_case",
"author").values(
*INVESTIGATION_CASE_RENDERER_FIELDS))
filename = "cases-{}.csv".format(timezone.localtime(timezone.now()))
headers = {"Content-Disposition": 'attachment; filename="{}"'.format(filename)}
return Response(data, headers=headers)
......@@ -10,6 +10,7 @@ django-cors-headers==3.2.0
django-filter==2.2.0
django-sendgrid-v5==0.8.1
djangorestframework==3.11.0
djangorestframework-csv==2.1.0
entrypoints==0.3
factory-boy==2.12.0
Faker==4.0.1
......@@ -31,3 +32,4 @@ sqlparse==0.3.0
text-unidecode==1.3
toml==0.10.0
typed-ast==1.4.1
unicodecsv==0.14.1
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment