From 67aed26e16fae367fbc52730972744e5f0a16c26 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 16:27:26 +0700 Subject: [PATCH 1/7] [RED] Add permission test for anonymous user --- apps/commons/tests/test_permissions.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/commons/tests/test_permissions.py b/apps/commons/tests/test_permissions.py index bdf73a7..ac933d8 100644 --- a/apps/commons/tests/test_permissions.py +++ b/apps/commons/tests/test_permissions.py @@ -19,6 +19,7 @@ class IsAuthenticatedPermissionTest(APITestCase): cls.user_1 = UserFactory(username="user_1", password="justpass") cls.user_2 = UserFactory(username="user_2", password="justpass") cls.user_3 = UserFactory(username="user_3", password="justpass") + cls.user_4 = UserFactory(username="user_4", password="justpass") cls.account_1 = AccountFactory( admin=True, @@ -32,7 +33,7 @@ class IsAuthenticatedPermissionTest(APITestCase): is_active=False, is_verified=True ) - cls.account_2 = AccountFactory( + cls.account_3 = AccountFactory( admin=False, user=cls.user_3, is_active=True, @@ -62,6 +63,12 @@ class IsAuthenticatedPermissionTest(APITestCase): request.user = self.user_3 self.assertFalse(self.permission.has_permission(request, None)) + def test_has_permission_false_for_anonymous_user(self): + self.client = APIClient() + request = self.client.get("/").wsgi_request + request.user = self.user_4 + self.assertFalse(self.permission.has_permission(request, None)) + class CreateOnlyPermissionTest(APITestCase): @classmethod -- GitLab From 4066c0ecbea050f5cd82bb8e15c85dd8b04f5b4f Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 16:28:14 +0700 Subject: [PATCH 2/7] [GREEN] Add handler for anonymous user on is authenticated permission --- apps/commons/permissions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/commons/permissions.py b/apps/commons/permissions.py index fca1109..2de9696 100644 --- a/apps/commons/permissions.py +++ b/apps/commons/permissions.py @@ -6,6 +6,10 @@ from django.core.exceptions import ObjectDoesNotExist class IsAuthenticated(BasePermission): def has_permission(self, request, view): user = request.user + + if not hasattr(user, "account"): + return False + return ( user.is_authenticated and user.account.is_active -- GitLab From 6122aa0259e5bc2e4410d7681fd887a9633055a3 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 16:34:08 +0700 Subject: [PATCH 3/7] [RED] Change test to assert renamed fields --- apps/exportables/constants.py | 36 +++++++++++++++++++ .../tests/test_units/test_exportables.py | 29 +++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/apps/exportables/constants.py b/apps/exportables/constants.py index b33f727..770f7a8 100644 --- a/apps/exportables/constants.py +++ b/apps/exportables/constants.py @@ -34,3 +34,39 @@ UNDETERMINED = "undetermined" # Total TOTAL = "total_count" + +# CSV Fields + +INVESTIGATION_CASE_RENDERER_FIELDS = [ + "case_subject__name", + "case_subject__address", + "case_subject__age", + "case_subject__is_male", + "case_subject__district", + "case_subject__sub_district", + "case_relation", + "reference_case__case_subject__name", + "outcome", + "medical_symptoms", + "risk_factors", + "medical_facility_reference", + "created_at", + "author__name", +] + +INVESTIGATION_CASE_HEADER_FIELDS = [ + "nama", + "alamat", + "usia", + "jenis_kelamin", + "kabupaten", + "kecamatan", + "jenis_kontak", + "subyek_kasus_acuan", + "hasil_pemeriksaan", + "gejala_medis", + "faktor_risiko", + "rujukan_fasilitas_kesehatan", + "tanggal_pencatatan", + "kader_pencatat", +] \ No newline at end of file diff --git a/apps/exportables/tests/test_units/test_exportables.py b/apps/exportables/tests/test_units/test_exportables.py index e3dd299..46fba1b 100644 --- a/apps/exportables/tests/test_units/test_exportables.py +++ b/apps/exportables/tests/test_units/test_exportables.py @@ -2,13 +2,22 @@ import json import csv import io from rest_framework import status -from rest_framework.test import APITestCase +from rest_framework.authtoken.models import Token +from rest_framework.test import APITestCase, APIClient + +from apps.constants import HEADER_PREFIX + +from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory 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 +from apps.exportables.constants import ( + INVESTIGATION_CASE_HEADER_FIELDS, + INVESTIGATION_CASE_RENDERER_FIELDS, +) + def init_data(): InvestigationCaseFactory( @@ -56,8 +65,15 @@ def init_data(): class ExportableViewTest(APITestCase): @classmethod def setUpTestData(cls): + cls.user = UserFactory(username="user", password="justpass") + cls.admin = AccountFactory(admin=True, user=cls.user) + cls.token, _ = Token.objects.get_or_create(user=cls.user) + init_data() + def setUp(self): + self.client = APIClient(HTTP_AUTHORIZATION=HEADER_PREFIX + self.token.key) + def test_exportable_data_return_values(self): url = "/exportables/" response = self.client.get(url) @@ -188,9 +204,16 @@ class ExportableViewTest(APITestCase): class ExportInvestigationCaseViewTest(APITestCase): @classmethod def setUpTestData(cls): + cls.user = UserFactory(username="user", password="justpass") + cls.admin = AccountFactory(admin=True, user=cls.user) + cls.token, _ = Token.objects.get_or_create(user=cls.user) cls.BASE_URL = "/exportables/investigation-cases-csv/" + init_data() + def setUp(self): + self.client = APIClient(HTTP_AUTHORIZATION=HEADER_PREFIX + self.token.key) + def export_csv_test_util(self, filter): url = self.BASE_URL + filter response = self.client.get(url) @@ -206,7 +229,7 @@ class ExportInvestigationCaseViewTest(APITestCase): body = list(reader) headers = body.pop(0) - self.assertEqual(headers, INVESTIGATION_CASE_RENDERER_FIELDS) + self.assertEqual(headers, INVESTIGATION_CASE_HEADER_FIELDS) return body def wrong_query_param_test_util(self, filter): -- GitLab From 34d913fc12b340b02a5f6a01e38ae4a018892960 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 16:59:49 +0700 Subject: [PATCH 4/7] [GREEN] Implement csv fields formatter and field renamer --- apps/exportables/renderers.py | 18 ++----------- apps/exportables/utils.py | 48 ++++++++++++++++++++++++++++++++++- apps/exportables/views.py | 36 ++++++++++++++++---------- 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/apps/exportables/renderers.py b/apps/exportables/renderers.py index e7c1cc3..f9780f5 100644 --- a/apps/exportables/renderers.py +++ b/apps/exportables/renderers.py @@ -1,21 +1,7 @@ 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", -] +from apps.exportables.constants import INVESTIGATION_CASE_HEADER_FIELDS class InvestigationCaseCSVRenderer(CSVRenderer): - header = INVESTIGATION_CASE_RENDERER_FIELDS + header = INVESTIGATION_CASE_HEADER_FIELDS diff --git a/apps/exportables/utils.py b/apps/exportables/utils.py index 24f32f7..a5c27bf 100644 --- a/apps/exportables/utils.py +++ b/apps/exportables/utils.py @@ -1,3 +1,7 @@ +import json +import time +from django.utils import timezone + from apps.exportables.constants import ( DISTRICT, DISTRICTS, @@ -6,10 +10,15 @@ from apps.exportables.constants import ( NEGATIVE, POSITIVE, TOTAL, - UNDETERMINED + UNDETERMINED, + INVESTIGATION_CASE_HEADER_FIELDS, + INVESTIGATION_CASE_RENDERER_FIELDS, ) +# CASE STATISTIC COUNTS + + def generate_initial_counts(): return { POSITIVE: 0, @@ -43,3 +52,40 @@ def map_sex_value(sex): if sex: return MALE return FEMALE + + +# CSV EXPORTS + + +def format_custom_csv_rows(cases_csv_rows): + + formated_rows = [] + FIELDS_LENGTH = len(INVESTIGATION_CASE_RENDERER_FIELDS) + + for row in cases_csv_rows: + + formated_row = {} + + for field_name_index in range(FIELDS_LENGTH): + original_field_name = INVESTIGATION_CASE_RENDERER_FIELDS[field_name_index] + target_field_name = INVESTIGATION_CASE_HEADER_FIELDS[field_name_index] + + formated_row[target_field_name] = row[original_field_name] + + value = formated_row["jenis_kelamin"] + formated_row["jenis_kelamin"] = "Laki-laki" if value else "Perempuan" + + value = json.loads(formated_row["gejala_medis"]) + selected_values = [key for key in value.keys() if value[key]] + formated_row["gejala_medis"] = "; ".join(selected_values) + + value = json.loads(formated_row["faktor_risiko"]) + selected_values = [key for key in value.keys() if value[key]] + formated_row["faktor_risiko"] = "; ".join(selected_values) + + value = formated_row["tanggal_pencatatan"] + formated_row["tanggal_pencatatan"] = timezone.localtime(value) + + formated_rows.append(formated_row) + + return formated_rows diff --git a/apps/exportables/views.py b/apps/exportables/views.py index 60c9878..1b3c551 100644 --- a/apps/exportables/views.py +++ b/apps/exportables/views.py @@ -9,6 +9,7 @@ from apps.cases.models import ( CaseSubject, InvestigationCase, ) +from apps.commons.permissions import IsAuthenticated from apps.exportables.constants import ( AGE, DISTRICT, @@ -19,31 +20,32 @@ from apps.exportables.constants import ( POSITIVE, SEX, TOTAL, - UNDETERMINED + UNDETERMINED, + INVESTIGATION_CASE_RENDERER_FIELDS, ) from apps.exportables.utils import ( + format_custom_csv_rows, generate_initial_counts, generate_initial_group_counts, map_outcome_value, map_sex_value, ) -from apps.exportables.renderers import ( - InvestigationCaseCSVRenderer, - INVESTIGATION_CASE_RENDERER_FIELDS, -) +from apps.exportables.renderers import InvestigationCaseCSVRenderer class CaseCountsView(APIView): + permission_classes = (IsAuthenticated,) def get(self, request, format=None): 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") - ) + investigation_cases = ( + InvestigationCase.objects + .values("case_subject") + .annotate(latest_investigation_case=Max("created_at")) + .order_by() + .values("case_subject__id", "is_positive") + ) outcomes = {} @@ -91,6 +93,7 @@ class CaseCountsView(APIView): class ExportInvestigationCaseView(APIView): + # permission_classes = (IsAuthenticated,) renderer_classes = (InvestigationCaseCSVRenderer,) def get(self, request, format=None): @@ -127,9 +130,14 @@ class ExportInvestigationCaseView(APIView): cases.prefetch_related( "case_subject", "reference_case", - "author").values( - *INVESTIGATION_CASE_RENDERER_FIELDS)) + "author" + ).values( + *INVESTIGATION_CASE_RENDERER_FIELDS + ) + ) + formated_data = format_custom_csv_rows(data) + filename = "cases-{}.csv".format(timezone.localtime(timezone.now())) headers = {"Content-Disposition": 'attachment; filename="{}"'.format(filename)} - return Response(data, headers=headers) + return Response(formated_data, headers=headers) -- GitLab From a1aa988ec1eb5365d6a6dfa47558709fd7936627 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 17:03:29 +0700 Subject: [PATCH 5/7] [GREEN] Fix linting issue --- apps/exportables/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/exportables/constants.py b/apps/exportables/constants.py index 770f7a8..9e7dd2b 100644 --- a/apps/exportables/constants.py +++ b/apps/exportables/constants.py @@ -69,4 +69,4 @@ INVESTIGATION_CASE_HEADER_FIELDS = [ "rujukan_fasilitas_kesehatan", "tanggal_pencatatan", "kader_pencatat", -] \ No newline at end of file +] -- GitLab From d44ef3d74b45feeda5531d2255cf45c46d2f4bc6 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 17:08:16 +0700 Subject: [PATCH 6/7] [REFACTOR] Enable permission on CSV export view --- apps/exportables/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/exportables/views.py b/apps/exportables/views.py index 1b3c551..bdf90d7 100644 --- a/apps/exportables/views.py +++ b/apps/exportables/views.py @@ -93,7 +93,7 @@ class CaseCountsView(APIView): class ExportInvestigationCaseView(APIView): - # permission_classes = (IsAuthenticated,) + permission_classes = (IsAuthenticated,) renderer_classes = (InvestigationCaseCSVRenderer,) def get(self, request, format=None): -- GitLab From dab2c5eb3818108a8d4ee460f852b776819c1900 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Thu, 4 Jun 2020 17:14:06 +0700 Subject: [PATCH 7/7] [REFACTOR] Fix variable naming --- apps/exportables/utils.py | 26 +++++++++++++------------- apps/exportables/views.py | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/exportables/utils.py b/apps/exportables/utils.py index a5c27bf..410fed7 100644 --- a/apps/exportables/utils.py +++ b/apps/exportables/utils.py @@ -59,33 +59,33 @@ def map_sex_value(sex): def format_custom_csv_rows(cases_csv_rows): - formated_rows = [] + formatted_rows = [] FIELDS_LENGTH = len(INVESTIGATION_CASE_RENDERER_FIELDS) for row in cases_csv_rows: - formated_row = {} + formatted_row = {} for field_name_index in range(FIELDS_LENGTH): original_field_name = INVESTIGATION_CASE_RENDERER_FIELDS[field_name_index] target_field_name = INVESTIGATION_CASE_HEADER_FIELDS[field_name_index] - formated_row[target_field_name] = row[original_field_name] + formatted_row[target_field_name] = row[original_field_name] - value = formated_row["jenis_kelamin"] - formated_row["jenis_kelamin"] = "Laki-laki" if value else "Perempuan" + value = formatted_row["jenis_kelamin"] + formatted_row["jenis_kelamin"] = "Laki-laki" if value else "Perempuan" - value = json.loads(formated_row["gejala_medis"]) + value = json.loads(formatted_row["gejala_medis"]) selected_values = [key for key in value.keys() if value[key]] - formated_row["gejala_medis"] = "; ".join(selected_values) + formatted_row["gejala_medis"] = "; ".join(selected_values) - value = json.loads(formated_row["faktor_risiko"]) + value = json.loads(formatted_row["faktor_risiko"]) selected_values = [key for key in value.keys() if value[key]] - formated_row["faktor_risiko"] = "; ".join(selected_values) + formatted_row["faktor_risiko"] = "; ".join(selected_values) - value = formated_row["tanggal_pencatatan"] - formated_row["tanggal_pencatatan"] = timezone.localtime(value) + value = formatted_row["tanggal_pencatatan"] + formatted_row["tanggal_pencatatan"] = timezone.localtime(value) - formated_rows.append(formated_row) + formatted_rows.append(formatted_row) - return formated_rows + return formatted_rows diff --git a/apps/exportables/views.py b/apps/exportables/views.py index bdf90d7..911ce7c 100644 --- a/apps/exportables/views.py +++ b/apps/exportables/views.py @@ -135,9 +135,9 @@ class ExportInvestigationCaseView(APIView): *INVESTIGATION_CASE_RENDERER_FIELDS ) ) - formated_data = format_custom_csv_rows(data) + formatted_data = format_custom_csv_rows(data) filename = "cases-{}.csv".format(timezone.localtime(timezone.now())) headers = {"Content-Disposition": 'attachment; filename="{}"'.format(filename)} - return Response(formated_data, headers=headers) + return Response(formatted_data, headers=headers) -- GitLab