From d63588f64abfcb343732860322247b502ae39b70 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 3 Mar 2020 11:05:36 +0700 Subject: [PATCH 01/11] [RED] Added tests for cases and case-subjects --- apps/accounts/tests/factories/accounts.py | 2 +- apps/cases/__init__.py | 0 apps/cases/admin.py | 3 + apps/cases/apps.py | 5 + apps/cases/migrations/__init__.py | 0 apps/cases/models.py | 3 + apps/cases/tests/__init__.py | 0 apps/cases/tests/factories/__init__.py | 0 apps/cases/tests/factories/case_subjects.py | 21 ++ apps/cases/tests/factories/cases.py | 44 ++++ apps/cases/tests/test_units/__init__.py | 0 .../tests/test_units/test_case_subjects.py | 193 ++++++++++++++++ .../test_units/test_investigation_cases.py | 207 ++++++++++++++++++ .../tests/test_units/test_monitoring_cases.py | 148 +++++++++++++ .../tests/test_units/test_positive_cases.py | 156 +++++++++++++ apps/cases/views.py | 3 + project/settings.py | 8 + 17 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 apps/cases/__init__.py create mode 100644 apps/cases/admin.py create mode 100644 apps/cases/apps.py create mode 100644 apps/cases/migrations/__init__.py create mode 100644 apps/cases/models.py create mode 100644 apps/cases/tests/__init__.py create mode 100644 apps/cases/tests/factories/__init__.py create mode 100644 apps/cases/tests/factories/case_subjects.py create mode 100644 apps/cases/tests/factories/cases.py create mode 100644 apps/cases/tests/test_units/__init__.py create mode 100644 apps/cases/tests/test_units/test_case_subjects.py create mode 100644 apps/cases/tests/test_units/test_investigation_cases.py create mode 100644 apps/cases/tests/test_units/test_monitoring_cases.py create mode 100644 apps/cases/tests/test_units/test_positive_cases.py create mode 100644 apps/cases/views.py diff --git a/apps/accounts/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py index 27ff6a7..4890b9f 100644 --- a/apps/accounts/tests/factories/accounts.py +++ b/apps/accounts/tests/factories/accounts.py @@ -12,7 +12,7 @@ class UserFactory(factory.DjangoModelFactory): class Meta: model = User - username = "username" + username = factory.Sequence(lambda n: "user_" + str(n)) password = "justpass" diff --git a/apps/cases/__init__.py b/apps/cases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cases/admin.py b/apps/cases/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/cases/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/cases/apps.py b/apps/cases/apps.py new file mode 100644 index 0000000..7c93236 --- /dev/null +++ b/apps/cases/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CasesConfig(AppConfig): + name = 'cases' diff --git a/apps/cases/migrations/__init__.py b/apps/cases/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cases/models.py b/apps/cases/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/cases/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/cases/tests/__init__.py b/apps/cases/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cases/tests/factories/__init__.py b/apps/cases/tests/factories/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cases/tests/factories/case_subjects.py b/apps/cases/tests/factories/case_subjects.py new file mode 100644 index 0000000..10f7837 --- /dev/null +++ b/apps/cases/tests/factories/case_subjects.py @@ -0,0 +1,21 @@ +import uuid +from apps.cases.models import CaseSubject + +from django.contrib.auth.models import User +from faker import Faker +import factory + + +faker = Faker() + + +class CaseSubjectFactory(factory.DjangoModelFactory): + class Meta: + model = CaseSubject + + name = faker.name() + age = 19 + is_male = True + address = faker.address() + district = faker.state() + sub_district = faker.city() diff --git a/apps/cases/tests/factories/cases.py b/apps/cases/tests/factories/cases.py new file mode 100644 index 0000000..5a202d1 --- /dev/null +++ b/apps/cases/tests/factories/cases.py @@ -0,0 +1,44 @@ +from apps.cases.models import ( + CaseSubject, + InvestigationCase, + PositiveCase, + MonitoringCase, +) + +from apps.accounts.tests.factories.accounts import AccountFactory +from apps.cases.tests.factories.case_subjects import CaseSubjectFactory + +from faker import Faker +import factory + + +faker = Faker() + + +class PositiveCaseFactory(factory.DjangoModelFactory): + class Meta: + model = PositiveCase + + case_subject = factory.SubFactory(CaseSubjectFactory) + author = factory.SubFactory(AccountFactory) + outcome = "Positive" + + +class InvestigationCaseFactory(factory.DjangoModelFactory): + class Meta: + model = InvestigationCase + + case_subject = factory.SubFactory(CaseSubjectFactory) + reference_case = factory.SubFactory(PositiveCaseFactory) + is_referral_needed = True + case_relation = "Family" + medical_facility_reference = "Hospital" + author = factory.SubFactory(AccountFactory) + + +class MonitoringCaseFactory(factory.DjangoModelFactory): + class Meta: + model = MonitoringCase + + author = factory.SubFactory(AccountFactory) + outcome = "Positive" diff --git a/apps/cases/tests/test_units/__init__.py b/apps/cases/tests/test_units/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cases/tests/test_units/test_case_subjects.py b/apps/cases/tests/test_units/test_case_subjects.py new file mode 100644 index 0000000..3ebe30b --- /dev/null +++ b/apps/cases/tests/test_units/test_case_subjects.py @@ -0,0 +1,193 @@ +import json +import pytz +from datetime import datetime +from django.test import TestCase +from rest_framework import status + +from apps.cases.models import CaseSubject +from apps.cases.tests.factories.case_subjects import CaseSubjectFactory + + +class CaseSubjectViewTest(TestCase): + def setUp(self): + self.case_subject_1 = CaseSubjectFactory(is_active=True) + self.case_subject_2 = CaseSubjectFactory( + is_active=True, deleted_at=datetime.now(tz=pytz.UTC) + ) + self.case_subject_3 = CaseSubjectFactory(is_active=False) + self.case_subject_4 = CaseSubjectFactory( + is_active=False, deleted_at=datetime.now(tz=pytz.UTC) + ) + + def test_list_case_subjects_only_shows_active_entries(self): + url = "/cases/case-subjects/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + data = [ + { + "revision_id": str(self.case_subject_1.revision_id), + "subject_id": str(self.case_subject_1.subject_id), + "name": self.case_subject_1.name, + "age": self.case_subject_1.age, + "is_male": self.case_subject_1.is_male, + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + }, + ] + self.assertJSONEqual(json.dumps(response.data), data) + + def test_retrieve_case_subject_success(self): + url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" + + response = self.client.get(url) + + data = { + "revision_id": str(self.case_subject_1.revision_id), + "subject_id": str(self.case_subject_1.subject_id), + "name": self.case_subject_1.name, + "age": self.case_subject_1.age, + "is_male": self.case_subject_1.is_male, + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + } + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertJSONEqual(json.dumps(response.data), data) + + def test_retrieve_case_subject_fails_on_deleted_or_inactive_subject(self): + url = "/cases/case-subjects/" + str(self.case_subject_2.revision_id) + "/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + url = "/cases/case-subjects/" + str(self.case_subject_3.revision_id) + "/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_create_new_case_subject_success(self): + url = "/cases/case-subjects/" + + case_subject_prev_active_count = CaseSubject.objects.active_revisions().count() + case_subject_prev_all_count = CaseSubject.objects.all().count() + + data = { + "name": self.case_subject_1.name, + "age": self.case_subject_1.age, + "is_male": self.case_subject_1.is_male, + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + case_subject_current_active_count = ( + CaseSubject.objects.active_revisions().count() + ) + case_subject_current_all_count = CaseSubject.objects.all().count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + case_subject_current_active_count, case_subject_prev_active_count + 1 + ) + self.assertEqual( + case_subject_current_all_count, case_subject_prev_all_count + 1 + ) + + def test_create_new_case_subject_fails_with_incomplete_data(self): + url = "/cases/case-subjects/" + + data = { + "is_male": self.case_subject_1.is_male, + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_edit_account_success(self): + url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" + + data = { + "subject_id": str(self.case_subject_1.subject_id), + "name": self.case_subject_1.name, + "age": self.case_subject_1.age, + "is_male": not self.case_subject_1.is_male, + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + } + + prev_all_case_subject_revision_count = CaseSubject.objects.all().count() + prev_active_case_subject_revision_count = ( + CaseSubject.objects.active_revisions().count() + ) + + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + response.data.pop("revision_id") + + current_case_subject_revision_count = CaseSubject.objects.all().count() + current_active_case_subject_revision_count = ( + CaseSubject.objects.active_revisions().count() + ) + + self.assertJSONEqual(json.dumps(response.data), data) + self.assertEqual( + current_case_subject_revision_count, + prev_all_case_subject_revision_count + 1, + ) + self.assertEqual( + current_active_case_subject_revision_count, + prev_active_case_subject_revision_count, + ) + + def test_edit_account_fails_with_incomplete_data(self): + url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" + + data = { + "address": self.case_subject_1.address, + "district": self.case_subject_1.district, + "sub_district": self.case_subject_1.sub_district, + } + + prev_all_case_subject_revision_count = CaseSubject.objects.all().count() + + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + current_all_case_subject_revision_count = CaseSubject.objects.all().count() + self.assertEqual( + current_all_case_subject_revision_count, + prev_all_case_subject_revision_count, + ) + + def test_soft_delete_case_subject_success(self): + url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" + + prev_deleted_all_count = CaseSubject.objects.all(with_deleted=True).count() + prev_deleted_count = CaseSubject.objects.deleted().count() + + response = self.client.delete( + path=url, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone( + CaseSubject.objects.get(revision_id=self.case_subject_1.revision_id) + ) + + current_deleted_all_count = CaseSubject.objects.all(with_deleted=True).count() + current_deleted_count = CaseSubject.objects.deleted().count() + + self.assertEqual(current_deleted_all_count, prev_deleted_all_count) + self.assertEqual(current_deleted_count, prev_deleted_count + 1) diff --git a/apps/cases/tests/test_units/test_investigation_cases.py b/apps/cases/tests/test_units/test_investigation_cases.py new file mode 100644 index 0000000..28378bf --- /dev/null +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -0,0 +1,207 @@ +import json +import pytz +from datetime import datetime +from django.test import TestCase +from rest_framework import status + +from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory +from apps.cases.models import PositiveCase, InvestigationCase +from apps.cases.tests.factories.case_subjects import CaseSubjectFactory +from apps.cases.tests.factories.cases import ( + PositiveCaseFactory, + InvestigationCaseFactory, +) + + +class InvestigationCaseViewTest(TestCase): + @classmethod + def setUpTestData(self): + self.user = UserFactory() + self.author = AccountFactory(user=self.user, admin=False) + self.case_subject = CaseSubjectFactory() + self.reference_case = PositiveCaseFactory( + case_subject=self.case_subject, author=self.author, + ) + self.case = InvestigationCaseFactory( + author=self.author, + case_subject=self.case_subject, + reference_case=self.reference_case, + ) + self.other_deleted_case = InvestigationCaseFactory( + author=self.author, + case_subject=self.case_subject, + reference_case=self.reference_case, + deleted_at=datetime.now(tz=pytz.UTC), + ) + self.other_inactive_case = InvestigationCaseFactory( + author=self.author, + case_subject=self.case_subject, + reference_case=self.reference_case, + is_active=False, + ) + + def test_list_investigation_cases_only_shows_active_entries(self): + url = "/cases/investigation-cases/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_investigation_case_success(self): + url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_investigation_case_fails_on_deleted_or_inactive_subject(self): + url = ( + "/cases/investigation-cases/" + + str(self.other_inactive_case.revision_id) + + "/" + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + url = ( + "/cases/investigation-cases/" + + str(self.other_deleted_case.revision_id) + + "/" + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_create_new_investigation_case_success(self): + url = "/cases/investigation-cases/" + + case_subject_prev_active_count = ( + InvestigationCase.objects.active_revisions().count() + ) + case_prev_all_count = InvestigationCase.objects.all().count() + + data = { + "reference_case": str(self.reference_case.revision_id), + "case_relation": self.case.case_relation, + "case_subject": str(self.case.case_subject.revision_id), + "medical_symptoms": self.case.medical_symptoms, + "risk_factors": self.case.risk_factors, + "is_referral_needed": self.case.is_referral_needed, + "medical_facility_reference": self.case.medical_facility_reference, + "author": str(self.case.author.id), + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + case_current_active_count = InvestigationCase.objects.active_revisions().count() + case_current_all_count = InvestigationCase.objects.all().count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(case_current_active_count, case_subject_prev_active_count + 1) + self.assertEqual(case_current_all_count, case_prev_all_count + 1) + + def test_create_new_investigation_case_fails_with_incomplete_data(self): + url = "/cases/investigation-cases/" + + data = { + "medical_facility_reference": self.case.medical_facility_reference, + "author": str(self.case.author.id), + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_edit_investigation_success(self): + url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" + + data = { + "case_subject": str(self.case_subject.revision_id), + "reference_case": str(self.reference_case.revision_id), + "case_relation": self.case.case_relation, + "medical_symptoms": self.case.medical_symptoms, + "risk_factors": self.case.risk_factors, + "is_referral_needed": self.case.is_referral_needed, + "medical_facility_reference": self.case.medical_facility_reference, + "author": str(self.case.author.id), + } + prev_all_investigation_case_revision_count = ( + InvestigationCase.objects.all().count() + ) + prev_active_investigation_case_revision_count = ( + InvestigationCase.objects.active_revisions().count() + ) + + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + current_investigation_case_revision_count = ( + InvestigationCase.objects.all().count() + ) + current_active_investigation_case_revision_count = ( + InvestigationCase.objects.active_revisions().count() + ) + + self.assertFalse( + InvestigationCase.objects.get(revision_id=self.case.revision_id).is_active + ) + self.assertEqual( + current_investigation_case_revision_count, + prev_all_investigation_case_revision_count + 1, + ) + self.assertEqual( + current_active_investigation_case_revision_count, + prev_active_investigation_case_revision_count, + ) + + def test_edit_investigation_fails_with_incomplete_data(self): + url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" + + data = { + "medical_facility_reference": "KUBURAN", + "author": str(self.case.author.id), + "case_subject": None, + } + + prev_active_investigation_case_revision_count = ( + InvestigationCase.objects.active_revisions().count() + ) + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + current_active_investigation_case_revision_count = ( + InvestigationCase.objects.active_revisions().count() + ) + self.assertEqual( + current_active_investigation_case_revision_count, + prev_active_investigation_case_revision_count, + ) + self.assertTrue( + InvestigationCase.objects.get(revision_id=self.case.revision_id).is_active + ) + + def test_soft_delete_investigation_case_success(self): + url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" + + prev_deleted_all_count = InvestigationCase.objects.all( + with_deleted=True + ).count() + prev_deleted_count = InvestigationCase.objects.deleted().count() + + response = self.client.delete( + path=url, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone( + InvestigationCase.objects.get(revision_id=self.case.revision_id) + ) + + current_deleted_all_count = InvestigationCase.objects.all( + with_deleted=True + ).count() + current_deleted_count = InvestigationCase.objects.deleted().count() + + self.assertEqual(current_deleted_all_count, prev_deleted_all_count) + self.assertEqual(current_deleted_count, prev_deleted_count + 1) diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py new file mode 100644 index 0000000..2f00f5e --- /dev/null +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -0,0 +1,148 @@ +import json +import pytz +from datetime import datetime +from django.core.exceptions import ValidationError +from django.test import TestCase +from rest_framework import status + +from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory +from apps.cases.models import MonitoringCase +from apps.cases.tests.factories.case_subjects import CaseSubjectFactory +from apps.cases.tests.factories.cases import ( + PositiveCaseFactory, + InvestigationCaseFactory, + MonitoringCaseFactory, +) + + +class MonitoringCaseViewTest(TestCase): + @classmethod + def setUpTestData(self): + self.user = UserFactory() + self.author = AccountFactory(user=self.user, admin=False) + self.case_subject = CaseSubjectFactory() + self.positive_case = PositiveCaseFactory( + author=self.author, case_subject=self.case_subject, + ) + self.case = MonitoringCaseFactory( + author=self.author, positive_case=self.positive_case, + ) + self.investigation_case = InvestigationCaseFactory( + author=self.author, + case_subject=self.case_subject, + reference_case=self.positive_case, + ) + self.other_deleted_case = MonitoringCaseFactory( + author=self.author, + positive_case=self.positive_case, + deleted_at=datetime.now(tz=pytz.UTC), + ) + self.other_inactive_case = MonitoringCaseFactory( + author=self.author, positive_case=self.positive_case, is_active=False, + ) + + def test_list_monitoring_cases_only_shows_active_entries(self): + url = "/cases/monitoring-cases/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_monitoring_case_success(self): + url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_monitoring_case_fails_on_deleted_or_inactive_subject(self): + url = ( + "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + url = ( + "/cases/monitoring-cases/" + str(self.other_deleted_case.revision_id) + "/" + ) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_create_new_monitoring_case_success(self): + url = "/cases/monitoring-cases/" + + case_prev_active_count = MonitoringCase.objects.active_revisions().count() + case_prev_all_count = MonitoringCase.objects.all().count() + + data = { + "positive_case": str(self.positive_case.revision_id), + "outcome": "Healed", + "author": str(self.case.author.id), + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + case_current_active_count = MonitoringCase.objects.active_revisions().count() + case_current_all_count = MonitoringCase.objects.all().count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(case_current_active_count, case_prev_active_count + 1) + self.assertEqual(case_current_all_count, case_prev_all_count + 1) + + def test_edit_investigation_success(self): + url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + + data = { + "positive_case": str(self.positive_case.revision_id), + "outcome": "Worsened", + "author": str(self.case.author.id), + } + prev_all_monitoring_case_revision_count = MonitoringCase.objects.all().count() + prev_active_monitoring_case_revision_count = ( + MonitoringCase.objects.active_revisions().count() + ) + + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + current_monitoring_case_revision_count = MonitoringCase.objects.all().count() + current_active_monitoring_case_revision_count = ( + MonitoringCase.objects.active_revisions().count() + ) + + self.assertFalse( + MonitoringCase.objects.get(revision_id=self.case.revision_id).is_active + ) + self.assertEqual( + current_monitoring_case_revision_count, + prev_all_monitoring_case_revision_count + 1, + ) + self.assertEqual( + current_active_monitoring_case_revision_count, + prev_active_monitoring_case_revision_count, + ) + + def test_monitoring_cannot_have_more_than_one_reference(self): + url = "/cases/monitoring-cases/" + data = { + "positive_case": str(self.positive_case.revision_id), + "investigation_case": str(self.investigation_case.revision_id), + "outcome": "Healed", + "author": str(self.case.author.id), + } + + with self.assertRaises(ValidationError): + self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + + def test_soft_delete_monitoring_case_success(self): + url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + response = self.client.delete( + path=url, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone( + MonitoringCase.objects.get(revision_id=self.case.revision_id) + ) diff --git a/apps/cases/tests/test_units/test_positive_cases.py b/apps/cases/tests/test_units/test_positive_cases.py new file mode 100644 index 0000000..611c91b --- /dev/null +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -0,0 +1,156 @@ +import json +import pytz +from datetime import datetime +from django.test import TestCase +from rest_framework import status + +from apps.accounts.tests.factories.accounts import AccountFactory, UserFactory +from apps.cases.models import PositiveCase +from apps.cases.tests.factories.case_subjects import CaseSubjectFactory +from apps.cases.tests.factories.cases import PositiveCaseFactory + + +class PositiveCaseViewTest(TestCase): + @classmethod + def setUpTestData(self): + self.user = UserFactory() + self.author = AccountFactory(user=self.user, admin=False) + self.case_subject = CaseSubjectFactory() + self.case = PositiveCaseFactory( + author=self.author, case_subject=self.case_subject, + ) + self.other_deleted_case = PositiveCaseFactory( + author=self.author, + case_subject=self.case_subject, + deleted_at=datetime.now(tz=pytz.UTC), + ) + self.other_inactive_case = PositiveCaseFactory( + author=self.author, case_subject=self.case_subject, is_active=False, + ) + + def test_list_positive_cases_only_shows_active_entries(self): + url = "/cases/positive-cases/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_positive_case_success(self): + url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_positive_case_fails_on_deleted_or_inactive_subject(self): + url = "/cases/positive-cases/" + str(self.other_inactive_case.revision_id) + "/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + url = "/cases/positive-cases/" + str(self.other_deleted_case.revision_id) + "/" + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_create_new_positive_case_success(self): + url = "/cases/positive-cases/" + + case_subject_prev_active_count = PositiveCase.objects.active_revisions().count() + case_prev_all_count = PositiveCase.objects.all().count() + + data = { + "case_subject": str(self.case.case_subject.revision_id), + "outcome": self.case.outcome, + "author": str(self.case.author.id), + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + case_current_active_count = PositiveCase.objects.active_revisions().count() + case_current_all_count = PositiveCase.objects.all().count() + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(case_current_active_count, case_subject_prev_active_count + 1) + self.assertEqual(case_current_all_count, case_prev_all_count + 1) + + def test_create_new_positive_case_fails_with_incomplete_data(self): + url = "/cases/positive-cases/" + + data = { + "case_subject": str(self.case.case_subject.revision_id), + "author": str(self.case.author.id), + } + + response = self.client.post( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_edit_positive_success(self): + url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" + + data = { + "case_subject": str(self.case_subject.revision_id), + "outcome": self.case.outcome, + "author": str(self.case.author.id), + } + prev_all_positive_case_revision_count = PositiveCase.objects.all().count() + prev_active_positive_case_revision_count = ( + PositiveCase.objects.active_revisions().count() + ) + + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + current_positive_case_revision_count = PositiveCase.objects.all().count() + current_active_positive_case_revision_count = ( + PositiveCase.objects.active_revisions().count() + ) + + self.assertFalse( + PositiveCase.objects.get(revision_id=self.case.revision_id).is_active + ) + self.assertEqual( + current_positive_case_revision_count, + prev_all_positive_case_revision_count + 1, + ) + self.assertEqual( + current_active_positive_case_revision_count, + prev_active_positive_case_revision_count, + ) + + def test_edit_positive_fails_with_incomplete_data(self): + url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" + + data = { + "case_subject": str(self.case_subject.revision_id), + "author": str(self.case.author.id), + } + + prev_active_positive_case_revision_count = ( + PositiveCase.objects.active_revisions().count() + ) + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + current_active_positive_case_revision_count = ( + PositiveCase.objects.active_revisions().count() + ) + self.assertEqual( + current_active_positive_case_revision_count, + prev_active_positive_case_revision_count, + ) + self.assertTrue( + PositiveCase.objects.get(revision_id=self.case.revision_id).is_active + ) + + def test_soft_delete_positive_case_success(self): + url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" + response = self.client.delete( + path=url, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsNotNone( + PositiveCase.objects.get(revision_id=self.case.revision_id) + ) diff --git a/apps/cases/views.py b/apps/cases/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/cases/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/project/settings.py b/project/settings.py index 6b28cd5..5c64c72 100644 --- a/project/settings.py +++ b/project/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "rest_framework", "apps.accounts", + "apps.cases", ] MIDDLEWARE = [ @@ -113,6 +114,13 @@ AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] +# Pagination + +REST_FRAMEWORK = { + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 10, +} + # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -- GitLab From 8661d02d70491547ed68aa678e2e7712e432b21b Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 3 Mar 2020 11:18:30 +0700 Subject: [PATCH 02/11] [RED] Added common soft object manager --- apps/commons/managers.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/commons/managers.py diff --git a/apps/commons/managers.py b/apps/commons/managers.py new file mode 100644 index 0000000..d9b778a --- /dev/null +++ b/apps/commons/managers.py @@ -0,0 +1,17 @@ +from django.db import models + + +class SoftObjectManager(models.Manager): + def all(self, with_deleted=False): + queryset = self.get_queryset() + if with_deleted: + return queryset + return queryset.filter(deleted_at__isnull=True) + + def deleted(self): + queryset = self.get_queryset() + return queryset.filter(deleted_at__isnull=False) + + def active_revisions(self): + queryset = self.get_queryset() + return queryset.filter(deleted_at__isnull=True, is_active=True) -- GitLab From 95e70681eae4cef8431275dd4a9ef77db02e4912 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 3 Mar 2020 20:03:39 +0700 Subject: [PATCH 03/11] [GREEN] Implement cases endpoints --- apps/cases/apps.py | 2 +- apps/cases/migrations/0001_initial.py | 211 ++++++++++++++++++++++++++ apps/cases/models.py | 141 ++++++++++++++++- apps/cases/serializers.py | 102 +++++++++++++ apps/cases/urls.py | 20 +++ apps/cases/views.py | 208 ++++++++++++++++++++++++- project/urls.py | 16 +- 7 files changed, 681 insertions(+), 19 deletions(-) create mode 100644 apps/cases/migrations/0001_initial.py create mode 100644 apps/cases/serializers.py create mode 100644 apps/cases/urls.py diff --git a/apps/cases/apps.py b/apps/cases/apps.py index 7c93236..648e0c7 100644 --- a/apps/cases/apps.py +++ b/apps/cases/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class CasesConfig(AppConfig): - name = 'cases' + name = "cases" diff --git a/apps/cases/migrations/0001_initial.py b/apps/cases/migrations/0001_initial.py new file mode 100644 index 0000000..fb5b76c --- /dev/null +++ b/apps/cases/migrations/0001_initial.py @@ -0,0 +1,211 @@ +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="CaseSubject", + fields=[ + ( + "revision_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("subject_id", models.UUIDField(default=uuid.uuid4)), + ("name", models.CharField(max_length=128)), + ("age", models.IntegerField()), + ("is_male", models.BooleanField(db_index=True)), + ("address", models.CharField(max_length=256)), + ("district", models.CharField(max_length=128)), + ("sub_district", models.CharField(max_length=128)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_active", models.BooleanField(db_index=True, default=True)), + ], + options={ + "verbose_name_plural": "case subjects", + "db_table": "case_subject", + }, + ), + migrations.CreateModel( + name="InvestigationCase", + fields=[ + ( + "revision_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("case_id", models.UUIDField(default=uuid.uuid4)), + ("case_relation", models.CharField(blank=True, max_length=128)), + ("medical_symptoms", models.TextField(default="{}")), + ("risk_factors", models.TextField(default="{}")), + ("is_referral_needed", models.BooleanField(db_index=True)), + ( + "medical_facility_reference", + models.CharField(blank=True, max_length=128), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_active", models.BooleanField(db_index=True, default=True)), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="investigation_case", + to="accounts.Account", + ), + ), + ( + "case_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="investigation_case", + to="cases.CaseSubject", + ), + ), + ], + options={ + "verbose_name_plural": "investigation cases", + "db_table": "investigation_case", + }, + ), + migrations.CreateModel( + name="PositiveCase", + fields=[ + ( + "revision_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("case_id", models.UUIDField(default=uuid.uuid4)), + ("outcome", models.CharField(max_length=256)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_active", models.BooleanField(db_index=True, default=True)), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="positive_case", + to="accounts.Account", + ), + ), + ( + "case_subject", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="positive_case", + to="cases.CaseSubject", + ), + ), + ], + options={ + "verbose_name_plural": "positive cases", + "db_table": "positive_case", + }, + ), + migrations.CreateModel( + name="MonitoringCase", + fields=[ + ( + "revision_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("case_id", models.UUIDField(default=uuid.uuid4)), + ( + "is_referred", + models.BooleanField(blank=True, db_index=True, null=True), + ), + ( + "is_checked", + models.BooleanField(blank=True, db_index=True, null=True), + ), + ("checking_date", models.DateField(blank=True, null=True)), + ( + "is_regularly_treated", + models.BooleanField(blank=True, db_index=True, null=True), + ), + ("treatment_start_date", models.DateField(blank=True, null=True)), + ("treatment_end_date", models.DateField(blank=True, null=True)), + ("outcome", models.CharField(blank=True, max_length=128)), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_active", models.BooleanField(db_index=True, default=True)), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="monitoring_case", + to="accounts.Account", + ), + ), + ( + "investigation_case", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="investigation_monitoring_case", + to="cases.InvestigationCase", + ), + ), + ( + "positive_case", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="positive_monitoring_case", + to="cases.PositiveCase", + ), + ), + ], + options={ + "verbose_name_plural": "monitoring cases", + "db_table": "monitoring_case", + }, + ), + migrations.AddField( + model_name="investigationcase", + name="reference_case", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="investigation_case", + to="cases.PositiveCase", + ), + ), + ] diff --git a/apps/cases/models.py b/apps/cases/models.py index 71a8362..d428393 100644 --- a/apps/cases/models.py +++ b/apps/cases/models.py @@ -1,3 +1,142 @@ +import uuid +from django.core.exceptions import ValidationError from django.db import models -# Create your models here. +from apps.accounts.models import Account +from apps.commons.managers import SoftObjectManager + + +class CaseSubject(models.Model): + revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + subject_id = models.UUIDField(default=uuid.uuid4) + name = models.CharField(max_length=128) + age = models.IntegerField() + is_male = models.BooleanField(db_index=True) + address = models.CharField(max_length=256) + district = models.CharField(max_length=128) + sub_district = models.CharField(max_length=128) + deleted_at = models.DateTimeField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + is_active = models.BooleanField(default=True, db_index=True) + + objects = SoftObjectManager() + + class Meta: + db_table = "case_subject" + verbose_name_plural = "case subjects" + + +class PositiveCase(models.Model): + revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + case_id = models.UUIDField(default=uuid.uuid4) + case_subject = models.ForeignKey( + CaseSubject, on_delete=models.CASCADE, related_name="positive_case" + ) + outcome = models.CharField(max_length=256) + author = models.ForeignKey( + Account, + blank=True, + null=True, + on_delete=models.DO_NOTHING, + related_name="positive_case", + ) + deleted_at = models.DateTimeField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + is_active = models.BooleanField(default=True, db_index=True) + + objects = SoftObjectManager() + + class Meta: + db_table = "positive_case" + verbose_name_plural = "positive cases" + + +class InvestigationCase(models.Model): + revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + case_id = models.UUIDField(default=uuid.uuid4) + case_subject = models.ForeignKey( + CaseSubject, on_delete=models.CASCADE, related_name="investigation_case" + ) + reference_case = models.ForeignKey( + PositiveCase, + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="investigation_case", + ) + case_relation = models.CharField(max_length=128, blank=True) + medical_symptoms = models.TextField(default="{}") + risk_factors = models.TextField(default="{}") + is_referral_needed = models.BooleanField(db_index=True) + medical_facility_reference = models.CharField(max_length=128, blank=True) + author = models.ForeignKey( + Account, + blank=True, + null=True, + on_delete=models.DO_NOTHING, + related_name="investigation_case", + ) + deleted_at = models.DateTimeField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + is_active = models.BooleanField(default=True, db_index=True) + + objects = SoftObjectManager() + + class Meta: + db_table = "investigation_case" + verbose_name_plural = "investigation cases" + + +class MonitoringCase(models.Model): + revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + case_id = models.UUIDField(default=uuid.uuid4) + investigation_case = models.ForeignKey( + InvestigationCase, + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="investigation_monitoring_case", + ) + positive_case = models.ForeignKey( + PositiveCase, + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="positive_monitoring_case", + ) + is_referred = models.BooleanField(blank=True, null=True, db_index=True) + is_checked = models.BooleanField(blank=True, null=True, db_index=True) + checking_date = models.DateField(blank=True, null=True) + is_regularly_treated = models.BooleanField(blank=True, null=True, db_index=True) + treatment_start_date = models.DateField(blank=True, null=True) + treatment_end_date = models.DateField(blank=True, null=True) + outcome = models.CharField(max_length=128, blank=True) + author = models.ForeignKey( + Account, + blank=True, + null=True, + on_delete=models.DO_NOTHING, + related_name="monitoring_case", + ) + deleted_at = models.DateTimeField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + is_active = models.BooleanField(default=True, db_index=True) + + objects = SoftObjectManager() + + class Meta: + db_table = "monitoring_case" + verbose_name_plural = "monitoring cases" + + def save(self, *args, **kwargs): + self.clean() + return super(MonitoringCase, self).save(*args, **kwargs) + + def clean(self): + super(MonitoringCase, self).clean() + + reference_cases = [self.positive_case, self.investigation_case] + if sum([case is not None for case in reference_cases]) > 1: + raise ValidationError( + "Monitoring Case should refer to only 1 type of case (Investigation|Positive)" + ) diff --git a/apps/cases/serializers.py b/apps/cases/serializers.py new file mode 100644 index 0000000..23b9996 --- /dev/null +++ b/apps/cases/serializers.py @@ -0,0 +1,102 @@ +from rest_framework import serializers +from apps.cases.models import ( + CaseSubject, + InvestigationCase, + MonitoringCase, + PositiveCase, +) + + +class CaseSubjectSerializer(serializers.ModelSerializer): + class Meta: + model = CaseSubject + fields = [ + "revision_id", + "subject_id", + "name", + "age", + "is_male", + "address", + "district", + "sub_district", + ] + + +class InvestigationCaseSerializer(serializers.ModelSerializer): + case_subject = CaseSubjectSerializer(read_only=True) + + class Meta: + model = InvestigationCase + fields = [ + "revision_id", + "case_id", + "case_subject", + "reference_case", + "case_relation", + "medical_symptoms", + "risk_factors", + "is_referral_needed", + "medical_facility_reference", + "author", + ] + + +class InvestigationCaseSummarySerializer(serializers.ModelSerializer): + class Meta: + model = InvestigationCase + fields = [ + "revision_id", + "case_id", + "case_subject", + "reference_case", + "case_relation", + "medical_symptoms", + "risk_factors", + "is_referral_needed", + "medical_facility_reference", + "author", + ] + + +class MonitoringCaseSerializer(serializers.ModelSerializer): + class Meta: + model = MonitoringCase + fields = [ + "revision_id", + "case_id", + "investigation_case", + "positive_case", + "is_referred", + "is_checked", + "is_regularly_treated", + "treatment_start_date", + "treatment_end_date", + "outcome", + "author", + ] + + +class PositiveCaseSerializer(serializers.ModelSerializer): + case_subject = CaseSubjectSerializer(read_only=True) + + class Meta: + model = PositiveCase + fields = [ + "revision_id", + "case_id", + "case_subject", + "outcome", + "author", + ] + + +class PositiveCaseSummarySerializer(serializers.ModelSerializer): + class Meta: + model = PositiveCase + fields = [ + "revision_id", + "case_id", + "case_subject", + "outcome", + "author", + ] diff --git a/apps/cases/urls.py b/apps/cases/urls.py new file mode 100644 index 0000000..7b01ff1 --- /dev/null +++ b/apps/cases/urls.py @@ -0,0 +1,20 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter + +from apps.cases.views import ( + CaseSubjectViewSet, + PositiveCaseViewSet, + MonitoringCaseViewSet, + InvestigationCaseViewSet, +) + + +router = DefaultRouter() +router.register(r"case-subjects", CaseSubjectViewSet) +router.register(r"positive-cases", PositiveCaseViewSet) +router.register(r"monitoring-cases", MonitoringCaseViewSet) +router.register(r"investigation-cases", InvestigationCaseViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] diff --git a/apps/cases/views.py b/apps/cases/views.py index 91ea44a..4d77e98 100644 --- a/apps/cases/views.py +++ b/apps/cases/views.py @@ -1,3 +1,207 @@ -from django.shortcuts import render +import pytz +from datetime import datetime +from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 +from rest_framework import status, viewsets +from rest_framework.response import Response -# Create your views here. +from apps.cases.models import ( + CaseSubject, + InvestigationCase, + MonitoringCase, + PositiveCase, +) +from apps.cases.serializers import ( + CaseSubjectSerializer, + InvestigationCaseSerializer, + InvestigationCaseSummarySerializer, + MonitoringCaseSerializer, + PositiveCaseSerializer, + PositiveCaseSummarySerializer, +) + + +class CaseSubjectViewSet(viewsets.ViewSet): + queryset = CaseSubject.objects.active_revisions() + serializer_class = CaseSubjectSerializer + + def list(self, request): + queryset = self.queryset + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def retrieve(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = self.serializer_class(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + CaseSubject.objects.create(**serializer.validated_data) + return Response(serializer.data, status=status.HTTP_201_CREATED,) + + def update(self, request, pk=None): + previous_instance = get_object_or_404(self.queryset, pk=pk) + serializer = self.serializer_class(previous_instance, data=request.data) + serializer.is_valid(raise_exception=True) + + serializer.validated_data.pop("subject_id") + new_instance = CaseSubject.objects.create( + **serializer.validated_data, + subject_id=previous_instance.subject_id, + is_active=True, + ) + + previous_instance.is_active = False + previous_instance.save() + + update_serializer = self.serializer_class(instance=new_instance) + + return Response(update_serializer.data, status=status.HTTP_200_OK) + + def destroy(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + instance.deleted_at = datetime.now(tz=pytz.UTC) + instance.save() + + serializer = self.serializer_class(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class InvestigationCaseViewSet(viewsets.ViewSet): + queryset = InvestigationCase.objects.active_revisions().select_related( + "case_subject" + ) + + def list(self, request): + queryset = self.queryset + serializer = InvestigationCaseSerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def retrieve(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = InvestigationCaseSerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + serializer = InvestigationCaseSummarySerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED,) + + def update(self, request, pk=None): + previous_instance = get_object_or_404(self.queryset, pk=pk) + serializer = InvestigationCaseSummarySerializer( + previous_instance, data=request.data + ) + serializer.is_valid(raise_exception=True) + + serializer_data = serializer.validated_data + new_instance = InvestigationCase.objects.create( + **serializer_data, case_id=previous_instance.case_id, is_active=True, + ) + previous_instance.is_active = False + previous_instance.save() + + update_serializer = InvestigationCaseSummarySerializer(instance=new_instance) + return Response(update_serializer.data, status=status.HTTP_200_OK) + + def destroy(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + instance.deleted_at = datetime.now(tz=pytz.UTC) + instance.save() + serializer = InvestigationCaseSummarySerializer(instance=instance) + + return Response(serializer.data, status=status.HTTP_200_OK) + + +class MonitoringCaseViewSet(viewsets.ViewSet): + queryset = MonitoringCase.objects.active_revisions().select_related( + "positive_case", "investigation_case", + ) + serializer_class = MonitoringCaseSerializer + + def list(self, request): + queryset = self.queryset + serializer = self.serializer_class(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def retrieve(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = self.serializer_class(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + serializer = self.serializer_class(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED,) + + def update(self, request, pk=None): + previous_instance = get_object_or_404(self.queryset, pk=pk) + serializer = self.serializer_class(previous_instance, data=request.data) + serializer.is_valid(raise_exception=True) + + serializer_data = serializer.validated_data + new_instance = MonitoringCase.objects.create( + **serializer_data, case_id=previous_instance.case_id, is_active=True, + ) + + previous_instance.is_active = False + previous_instance.save() + + update_serializer = self.serializer_class(instance=new_instance) + return Response(update_serializer.data, status=status.HTTP_200_OK) + + def destroy(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + instance.deleted_at = datetime.now(tz=pytz.UTC) + instance.save() + + serializer = MonitoringCaseSerializer(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class PositiveCaseViewSet(viewsets.ViewSet): + queryset = PositiveCase.objects.active_revisions().select_related("case_subject") + + def list(self, request): + queryset = self.queryset + serializer = PositiveCaseSerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def retrieve(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + serializer = PositiveCaseSerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + serializer = PositiveCaseSummarySerializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED,) + + def update(self, request, pk=None): + previous_instance = get_object_or_404(self.queryset, pk=pk) + serializer = PositiveCaseSummarySerializer(previous_instance, data=request.data) + serializer.is_valid(raise_exception=True) + new_instance = PositiveCase.objects.create( + case_id=previous_instance.case_id, + is_active=True, + **serializer.validated_data, + ) + + previous_instance.is_active = False + previous_instance.save() + + update_serializer = PositiveCaseSummarySerializer(instance=new_instance) + return Response(update_serializer.data, status=status.HTTP_200_OK) + + def destroy(self, request, pk=None): + instance = get_object_or_404(self.queryset, pk=pk) + instance.deleted_at = datetime.now(tz=pytz.UTC) + instance.save() + + serializer = PositiveCaseSerializer(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/project/urls.py b/project/urls.py index d2bd5c3..50182b1 100644 --- a/project/urls.py +++ b/project/urls.py @@ -1,22 +1,8 @@ -"""app URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin from django.urls import path, include urlpatterns = [ path("admin/", admin.site.urls), path("accounts/", include("apps.accounts.urls")), + path("cases/", include("apps.cases.urls")), ] -- GitLab From ef5b370bcbd0d06cb9464eace76c8d3741451a40 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 3 Mar 2020 20:12:24 +0700 Subject: [PATCH 04/11] [CHORES] Change serializer semantic name --- apps/accounts/serializers.py | 2 +- apps/accounts/views.py | 10 +++++----- apps/cases/serializers.py | 10 +++++----- apps/cases/views.py | 34 +++++++++++++++++----------------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py index fbf73f7..18cfbc2 100644 --- a/apps/accounts/serializers.py +++ b/apps/accounts/serializers.py @@ -2,7 +2,7 @@ from rest_framework import serializers from apps.accounts.models import Account -class AccountSummarySerializer(serializers.ModelSerializer): +class AccountSerializer(serializers.ModelSerializer): username = serializers.CharField(source="user.username", required=False) class Meta: diff --git a/apps/accounts/views.py b/apps/accounts/views.py index c943a17..da6af13 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -5,7 +5,7 @@ from rest_framework.response import Response from apps.accounts.models import Account from apps.accounts.serializers import ( - AccountSummarySerializer, + AccountSerializer, AccountRegisterSerializer, ) @@ -15,12 +15,12 @@ class AccountViewSet(viewsets.ViewSet): def list(self, request): queryset = self.queryset - serializer = AccountSummarySerializer(queryset, many=True) + serializer = AccountSerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - serializer = AccountSummarySerializer(instance) + serializer = AccountSerializer(instance) return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request): @@ -34,12 +34,12 @@ class AccountViewSet(viewsets.ViewSet): account = Account.objects.create(user=user, **serializer.validated_data) return Response( - AccountSummarySerializer(account).data, status=status.HTTP_201_CREATED, + AccountSerializer(account).data, status=status.HTTP_201_CREATED, ) def update(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - serializer = AccountSummarySerializer(instance, data=request.data) + serializer = AccountSerializer(instance, data=request.data) serializer.is_valid(raise_exception=True) serializer.save() diff --git a/apps/cases/serializers.py b/apps/cases/serializers.py index 23b9996..1fc6920 100644 --- a/apps/cases/serializers.py +++ b/apps/cases/serializers.py @@ -22,7 +22,7 @@ class CaseSubjectSerializer(serializers.ModelSerializer): ] -class InvestigationCaseSerializer(serializers.ModelSerializer): +class InvestigationCaseSummarySerializer(serializers.ModelSerializer): case_subject = CaseSubjectSerializer(read_only=True) class Meta: @@ -41,7 +41,7 @@ class InvestigationCaseSerializer(serializers.ModelSerializer): ] -class InvestigationCaseSummarySerializer(serializers.ModelSerializer): +class InvestigationCaseSerializer(serializers.ModelSerializer): class Meta: model = InvestigationCase fields = [ @@ -58,7 +58,7 @@ class InvestigationCaseSummarySerializer(serializers.ModelSerializer): ] -class MonitoringCaseSerializer(serializers.ModelSerializer): +class MonitoringCaseSummarySerializer(serializers.ModelSerializer): class Meta: model = MonitoringCase fields = [ @@ -76,7 +76,7 @@ class MonitoringCaseSerializer(serializers.ModelSerializer): ] -class PositiveCaseSerializer(serializers.ModelSerializer): +class PositiveCaseSummarySerializer(serializers.ModelSerializer): case_subject = CaseSubjectSerializer(read_only=True) class Meta: @@ -90,7 +90,7 @@ class PositiveCaseSerializer(serializers.ModelSerializer): ] -class PositiveCaseSummarySerializer(serializers.ModelSerializer): +class PositiveCaseSerializer(serializers.ModelSerializer): class Meta: model = PositiveCase fields = [ diff --git a/apps/cases/views.py b/apps/cases/views.py index 4d77e98..daac2fb 100644 --- a/apps/cases/views.py +++ b/apps/cases/views.py @@ -13,11 +13,11 @@ from apps.cases.models import ( ) from apps.cases.serializers import ( CaseSubjectSerializer, - InvestigationCaseSerializer, InvestigationCaseSummarySerializer, - MonitoringCaseSerializer, - PositiveCaseSerializer, + InvestigationCaseSerializer, + MonitoringCaseSummarySerializer, PositiveCaseSummarySerializer, + PositiveCaseSerializer, ) @@ -76,23 +76,23 @@ class InvestigationCaseViewSet(viewsets.ViewSet): def list(self, request): queryset = self.queryset - serializer = InvestigationCaseSerializer(queryset, many=True) + serializer = InvestigationCaseSummarySerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - serializer = InvestigationCaseSerializer(instance) + serializer = InvestigationCaseSummarySerializer(instance) return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request): - serializer = InvestigationCaseSummarySerializer(data=request.data) + serializer = InvestigationCaseSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED,) def update(self, request, pk=None): previous_instance = get_object_or_404(self.queryset, pk=pk) - serializer = InvestigationCaseSummarySerializer( + serializer = InvestigationCaseSerializer( previous_instance, data=request.data ) serializer.is_valid(raise_exception=True) @@ -104,14 +104,14 @@ class InvestigationCaseViewSet(viewsets.ViewSet): previous_instance.is_active = False previous_instance.save() - update_serializer = InvestigationCaseSummarySerializer(instance=new_instance) + update_serializer = InvestigationCaseSerializer(instance=new_instance) return Response(update_serializer.data, status=status.HTTP_200_OK) def destroy(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) instance.deleted_at = datetime.now(tz=pytz.UTC) instance.save() - serializer = InvestigationCaseSummarySerializer(instance=instance) + serializer = InvestigationCaseSerializer(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) @@ -120,7 +120,7 @@ class MonitoringCaseViewSet(viewsets.ViewSet): queryset = MonitoringCase.objects.active_revisions().select_related( "positive_case", "investigation_case", ) - serializer_class = MonitoringCaseSerializer + serializer_class = MonitoringCaseSummarySerializer def list(self, request): queryset = self.queryset @@ -159,7 +159,7 @@ class MonitoringCaseViewSet(viewsets.ViewSet): instance.deleted_at = datetime.now(tz=pytz.UTC) instance.save() - serializer = MonitoringCaseSerializer(instance=instance) + serializer = MonitoringCaseSummarySerializer(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) @@ -168,23 +168,23 @@ class PositiveCaseViewSet(viewsets.ViewSet): def list(self, request): queryset = self.queryset - serializer = PositiveCaseSerializer(queryset, many=True) + serializer = PositiveCaseSummarySerializer(queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - serializer = PositiveCaseSerializer(instance) + serializer = PositiveCaseSummarySerializer(instance) return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request): - serializer = PositiveCaseSummarySerializer(data=request.data) + serializer = PositiveCaseSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED,) def update(self, request, pk=None): previous_instance = get_object_or_404(self.queryset, pk=pk) - serializer = PositiveCaseSummarySerializer(previous_instance, data=request.data) + serializer = PositiveCaseSerializer(previous_instance, data=request.data) serializer.is_valid(raise_exception=True) new_instance = PositiveCase.objects.create( case_id=previous_instance.case_id, @@ -195,7 +195,7 @@ class PositiveCaseViewSet(viewsets.ViewSet): previous_instance.is_active = False previous_instance.save() - update_serializer = PositiveCaseSummarySerializer(instance=new_instance) + update_serializer = PositiveCaseSerializer(instance=new_instance) return Response(update_serializer.data, status=status.HTTP_200_OK) def destroy(self, request, pk=None): @@ -203,5 +203,5 @@ class PositiveCaseViewSet(viewsets.ViewSet): instance.deleted_at = datetime.now(tz=pytz.UTC) instance.save() - serializer = PositiveCaseSerializer(instance=instance) + serializer = PositiveCaseSummarySerializer(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) -- GitLab From a3d4217985f736f2de532c90f8a9108bb4673244 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Tue, 3 Mar 2020 20:33:12 +0700 Subject: [PATCH 05/11] [REFACTOR] assign deletion logic to model.delete() --- apps/cases/models.py | 26 ++++++++++++++++++++++++++ apps/cases/views.py | 16 ++++------------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/apps/cases/models.py b/apps/cases/models.py index d428393..8ad9628 100644 --- a/apps/cases/models.py +++ b/apps/cases/models.py @@ -1,4 +1,6 @@ import uuid +import pytz +from datetime import datetime from django.core.exceptions import ValidationError from django.db import models @@ -24,6 +26,12 @@ class CaseSubject(models.Model): class Meta: db_table = "case_subject" verbose_name_plural = "case subjects" + + def delete(self): + if self.deleted_at is None: + self.is_active = False + self.deleted_at = datetime.now(tz=pytz.UTC) + self.save() class PositiveCase(models.Model): @@ -50,6 +58,12 @@ class PositiveCase(models.Model): db_table = "positive_case" verbose_name_plural = "positive cases" + def delete(self): + if self.deleted_at is None: + self.is_active = False + self.deleted_at = datetime.now(tz=pytz.UTC) + self.save() + class InvestigationCase(models.Model): revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -86,6 +100,12 @@ class InvestigationCase(models.Model): db_table = "investigation_case" verbose_name_plural = "investigation cases" + def delete(self): + if self.deleted_at is None: + self.is_active = False + self.deleted_at = datetime.now(tz=pytz.UTC) + self.save() + class MonitoringCase(models.Model): revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) @@ -140,3 +160,9 @@ class MonitoringCase(models.Model): raise ValidationError( "Monitoring Case should refer to only 1 type of case (Investigation|Positive)" ) + + def delete(self): + if self.deleted_at is None: + self.is_active = False + self.deleted_at = datetime.now(tz=pytz.UTC) + self.save() diff --git a/apps/cases/views.py b/apps/cases/views.py index daac2fb..e4c109d 100644 --- a/apps/cases/views.py +++ b/apps/cases/views.py @@ -62,9 +62,7 @@ class CaseSubjectViewSet(viewsets.ViewSet): def destroy(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - instance.deleted_at = datetime.now(tz=pytz.UTC) - instance.save() - + instance.delete() serializer = self.serializer_class(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) @@ -109,10 +107,8 @@ class InvestigationCaseViewSet(viewsets.ViewSet): def destroy(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - instance.deleted_at = datetime.now(tz=pytz.UTC) - instance.save() + instance.delete() serializer = InvestigationCaseSerializer(instance=instance) - return Response(serializer.data, status=status.HTTP_200_OK) @@ -156,9 +152,7 @@ class MonitoringCaseViewSet(viewsets.ViewSet): def destroy(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - instance.deleted_at = datetime.now(tz=pytz.UTC) - instance.save() - + instance.delete() serializer = MonitoringCaseSummarySerializer(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) @@ -200,8 +194,6 @@ class PositiveCaseViewSet(viewsets.ViewSet): def destroy(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - instance.deleted_at = datetime.now(tz=pytz.UTC) - instance.save() - + instance.delete() serializer = PositiveCaseSummarySerializer(instance=instance) return Response(serializer.data, status=status.HTTP_200_OK) -- GitLab From ec5607bdbb3278d6b6c6f161e7f1a152d2c68831 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 14:46:20 +0700 Subject: [PATCH 06/11] [CHORES] Fix styling issues --- apps/accounts/migrations/0001_initial.py | 41 +++++++++++++++--------- apps/accounts/models.py | 1 + apps/cases/models.py | 2 +- apps/cases/views.py | 4 +-- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index c9e390e..e6e7d22 100644 --- a/apps/accounts/migrations/0001_initial.py +++ b/apps/accounts/migrations/0001_initial.py @@ -16,22 +16,33 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Account', + name="Account", fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=128)), - ('email', models.EmailField(max_length=128)), - ('phone_number', models.CharField(max_length=64)), - ('area', models.CharField(max_length=128)), - ('is_admin', models.BooleanField(default=False)), - ('is_verified', models.BooleanField(default=False)), - ('is_active', models.BooleanField(default=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=128)), + ("email", models.EmailField(max_length=128)), + ("phone_number", models.CharField(max_length=64)), + ("area", models.CharField(max_length=128)), + ("is_admin", models.BooleanField(default=False)), + ("is_verified", models.BooleanField(default=False)), + ("is_active", models.BooleanField(default=False)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], - options={ - 'verbose_name_plural': 'accounts', - 'db_table': 'account', - }, + options={"verbose_name_plural": "accounts", "db_table": "account",}, ), ] diff --git a/apps/accounts/models.py b/apps/accounts/models.py index 058490b..ecdcf05 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -2,6 +2,7 @@ import uuid from django.contrib.auth.models import User from django.db import models + class Account(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user = models.OneToOneField(User, on_delete=models.CASCADE) diff --git a/apps/cases/models.py b/apps/cases/models.py index 8ad9628..8e42cdc 100644 --- a/apps/cases/models.py +++ b/apps/cases/models.py @@ -26,7 +26,7 @@ class CaseSubject(models.Model): class Meta: db_table = "case_subject" verbose_name_plural = "case subjects" - + def delete(self): if self.deleted_at is None: self.is_active = False diff --git a/apps/cases/views.py b/apps/cases/views.py index e4c109d..d6f0984 100644 --- a/apps/cases/views.py +++ b/apps/cases/views.py @@ -90,9 +90,7 @@ class InvestigationCaseViewSet(viewsets.ViewSet): def update(self, request, pk=None): previous_instance = get_object_or_404(self.queryset, pk=pk) - serializer = InvestigationCaseSerializer( - previous_instance, data=request.data - ) + serializer = InvestigationCaseSerializer(previous_instance, data=request.data) serializer.is_valid(raise_exception=True) serializer_data = serializer.validated_data -- GitLab From f2b6b395635e3a411ffb804dd3d99c3f255c40de Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 19:21:15 +0700 Subject: [PATCH 07/11] [RED] Modify tests for reference by case id --- apps/accounts/tests/factories/accounts.py | 1 - apps/cases/tests/factories/cases.py | 3 -- .../tests/test_units/test_case_subjects.py | 8 ++++-- .../test_units/test_investigation_cases.py | 28 +++++++++---------- .../tests/test_units/test_monitoring_cases.py | 24 ++++++++-------- .../tests/test_units/test_positive_cases.py | 18 ++++++------ 6 files changed, 41 insertions(+), 41 deletions(-) diff --git a/apps/accounts/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py index 4890b9f..f20e27e 100644 --- a/apps/accounts/tests/factories/accounts.py +++ b/apps/accounts/tests/factories/accounts.py @@ -13,7 +13,6 @@ class UserFactory(factory.DjangoModelFactory): model = User username = factory.Sequence(lambda n: "user_" + str(n)) - password = "justpass" class AccountFactory(factory.DjangoModelFactory): diff --git a/apps/cases/tests/factories/cases.py b/apps/cases/tests/factories/cases.py index 5a202d1..92bfb0d 100644 --- a/apps/cases/tests/factories/cases.py +++ b/apps/cases/tests/factories/cases.py @@ -19,7 +19,6 @@ class PositiveCaseFactory(factory.DjangoModelFactory): class Meta: model = PositiveCase - case_subject = factory.SubFactory(CaseSubjectFactory) author = factory.SubFactory(AccountFactory) outcome = "Positive" @@ -28,8 +27,6 @@ class InvestigationCaseFactory(factory.DjangoModelFactory): class Meta: model = InvestigationCase - case_subject = factory.SubFactory(CaseSubjectFactory) - reference_case = factory.SubFactory(PositiveCaseFactory) is_referral_needed = True case_relation = "Family" medical_facility_reference = "Hospital" diff --git a/apps/cases/tests/test_units/test_case_subjects.py b/apps/cases/tests/test_units/test_case_subjects.py index 3ebe30b..f3eb2f6 100644 --- a/apps/cases/tests/test_units/test_case_subjects.py +++ b/apps/cases/tests/test_units/test_case_subjects.py @@ -34,6 +34,7 @@ class CaseSubjectViewTest(TestCase): "address": self.case_subject_1.address, "district": self.case_subject_1.district, "sub_district": self.case_subject_1.sub_district, + "is_active" : self.case_subject_1.is_active, }, ] self.assertJSONEqual(json.dumps(response.data), data) @@ -52,6 +53,7 @@ class CaseSubjectViewTest(TestCase): "address": self.case_subject_1.address, "district": self.case_subject_1.district, "sub_district": self.case_subject_1.sub_district, + "is_active" : self.case_subject_1.is_active, } self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -84,6 +86,8 @@ class CaseSubjectViewTest(TestCase): response = self.client.post( path=url, data=data, content_type="application/json", format="json", ) + response.data["is_active"] = self.case_subject_1.is_active + case_subject_current_active_count = ( CaseSubject.objects.active_revisions().count() ) @@ -134,6 +138,7 @@ class CaseSubjectViewTest(TestCase): path=url, data=data, content_type="application/json", format="json", ) response.data.pop("revision_id") + response.data.pop("is_active") current_case_subject_revision_count = CaseSubject.objects.all().count() current_active_case_subject_revision_count = ( @@ -174,7 +179,6 @@ class CaseSubjectViewTest(TestCase): def test_soft_delete_case_subject_success(self): url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" - prev_deleted_all_count = CaseSubject.objects.all(with_deleted=True).count() prev_deleted_count = CaseSubject.objects.deleted().count() @@ -183,7 +187,7 @@ class CaseSubjectViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - CaseSubject.objects.get(revision_id=self.case_subject_1.revision_id) + CaseSubject.objects.all(with_deleted=True).filter(revision_id=self.case_subject_1.revision_id).first() ) current_deleted_all_count = CaseSubject.objects.all(with_deleted=True).count() diff --git a/apps/cases/tests/test_units/test_investigation_cases.py b/apps/cases/tests/test_units/test_investigation_cases.py index 28378bf..2e34e6d 100644 --- a/apps/cases/tests/test_units/test_investigation_cases.py +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -20,27 +20,27 @@ class InvestigationCaseViewTest(TestCase): self.author = AccountFactory(user=self.user, admin=False) self.case_subject = CaseSubjectFactory() self.reference_case = PositiveCaseFactory( - case_subject=self.case_subject, author=self.author, + case_subject_id=self.case_subject.subject_id, author=self.author, ) self.case = InvestigationCaseFactory( author=self.author, - case_subject=self.case_subject, - reference_case=self.reference_case, + case_subject_id=self.case_subject.subject_id, + reference_case_id=self.reference_case.case_id, ) self.other_deleted_case = InvestigationCaseFactory( author=self.author, - case_subject=self.case_subject, - reference_case=self.reference_case, + case_subject_id=self.case_subject.subject_id, + reference_case_id=self.reference_case.case_id, deleted_at=datetime.now(tz=pytz.UTC), ) self.other_inactive_case = InvestigationCaseFactory( author=self.author, - case_subject=self.case_subject, - reference_case=self.reference_case, + case_subject_id=self.case_subject.subject_id, + reference_case_id=self.reference_case.case_id, is_active=False, ) - def test_list_investigation_cases_only_shows_active_entries(self): + def test_list_investigation_cases_success(self): url = "/cases/investigation-cases/" response = self.client.get(url) @@ -78,9 +78,9 @@ class InvestigationCaseViewTest(TestCase): case_prev_all_count = InvestigationCase.objects.all().count() data = { - "reference_case": str(self.reference_case.revision_id), + "reference_case_id": str(self.reference_case.case_id), "case_relation": self.case.case_relation, - "case_subject": str(self.case.case_subject.revision_id), + "case_subject_id": str(self.case.case_subject.subject_id), "medical_symptoms": self.case.medical_symptoms, "risk_factors": self.case.risk_factors, "is_referral_needed": self.case.is_referral_needed, @@ -115,8 +115,8 @@ class InvestigationCaseViewTest(TestCase): url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" data = { - "case_subject": str(self.case_subject.revision_id), - "reference_case": str(self.reference_case.revision_id), + "case_subject_id": str(self.case_subject.subject_id), + "reference_case_id": str(self.reference_case.case_id), "case_relation": self.case.case_relation, "medical_symptoms": self.case.medical_symptoms, "risk_factors": self.case.risk_factors, @@ -161,7 +161,7 @@ class InvestigationCaseViewTest(TestCase): data = { "medical_facility_reference": "KUBURAN", "author": str(self.case.author.id), - "case_subject": None, + "case_subject_id": None, } prev_active_investigation_case_revision_count = ( @@ -195,7 +195,7 @@ class InvestigationCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - InvestigationCase.objects.get(revision_id=self.case.revision_id) + InvestigationCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() ) current_deleted_all_count = InvestigationCase.objects.all( diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py index 2f00f5e..fa4d8c0 100644 --- a/apps/cases/tests/test_units/test_monitoring_cases.py +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -22,26 +22,26 @@ class MonitoringCaseViewTest(TestCase): self.author = AccountFactory(user=self.user, admin=False) self.case_subject = CaseSubjectFactory() self.positive_case = PositiveCaseFactory( - author=self.author, case_subject=self.case_subject, + author=self.author, case_subject_id=self.case_subject.subject_id, ) self.case = MonitoringCaseFactory( - author=self.author, positive_case=self.positive_case, + author=self.author, positive_case_id=self.positive_case.case_id, ) self.investigation_case = InvestigationCaseFactory( author=self.author, - case_subject=self.case_subject, - reference_case=self.positive_case, + case_subject_id=self.case_subject.subject_id, + reference_case_id=self.positive_case.case_id, ) self.other_deleted_case = MonitoringCaseFactory( author=self.author, - positive_case=self.positive_case, + positive_case_id=self.positive_case.case_id, deleted_at=datetime.now(tz=pytz.UTC), ) self.other_inactive_case = MonitoringCaseFactory( - author=self.author, positive_case=self.positive_case, is_active=False, + author=self.author, positive_case_id=self.positive_case.case_id, is_active=False, ) - def test_list_monitoring_cases_only_shows_active_entries(self): + def test_list_monitoring_cases_success(self): url = "/cases/monitoring-cases/" response = self.client.get(url) @@ -73,7 +73,7 @@ class MonitoringCaseViewTest(TestCase): case_prev_all_count = MonitoringCase.objects.all().count() data = { - "positive_case": str(self.positive_case.revision_id), + "positive_case_id": str(self.positive_case.case_id), "outcome": "Healed", "author": str(self.case.author.id), } @@ -92,7 +92,7 @@ class MonitoringCaseViewTest(TestCase): url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" data = { - "positive_case": str(self.positive_case.revision_id), + "positive_case_id": str(self.positive_case.revision_id), "outcome": "Worsened", "author": str(self.case.author.id), } @@ -126,8 +126,8 @@ class MonitoringCaseViewTest(TestCase): def test_monitoring_cannot_have_more_than_one_reference(self): url = "/cases/monitoring-cases/" data = { - "positive_case": str(self.positive_case.revision_id), - "investigation_case": str(self.investigation_case.revision_id), + "positive_case_id": str(self.positive_case.revision_id), + "investigation_case_id": str(self.investigation_case.revision_id), "outcome": "Healed", "author": str(self.case.author.id), } @@ -144,5 +144,5 @@ class MonitoringCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - MonitoringCase.objects.get(revision_id=self.case.revision_id) + MonitoringCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() ) diff --git a/apps/cases/tests/test_units/test_positive_cases.py b/apps/cases/tests/test_units/test_positive_cases.py index 611c91b..fd292c7 100644 --- a/apps/cases/tests/test_units/test_positive_cases.py +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -17,18 +17,18 @@ class PositiveCaseViewTest(TestCase): self.author = AccountFactory(user=self.user, admin=False) self.case_subject = CaseSubjectFactory() self.case = PositiveCaseFactory( - author=self.author, case_subject=self.case_subject, + author=self.author, case_subject_id=self.case_subject.subject_id, ) self.other_deleted_case = PositiveCaseFactory( author=self.author, - case_subject=self.case_subject, + case_subject_id=self.case_subject.subject_id, deleted_at=datetime.now(tz=pytz.UTC), ) self.other_inactive_case = PositiveCaseFactory( - author=self.author, case_subject=self.case_subject, is_active=False, + author=self.author, case_subject_id=self.case_subject.subject_id, is_active=False, ) - def test_list_positive_cases_only_shows_active_entries(self): + def test_list_positive_cases_success(self): url = "/cases/positive-cases/" response = self.client.get(url) @@ -56,7 +56,7 @@ class PositiveCaseViewTest(TestCase): case_prev_all_count = PositiveCase.objects.all().count() data = { - "case_subject": str(self.case.case_subject.revision_id), + "case_subject_id": str(self.case.case_subject_id), "outcome": self.case.outcome, "author": str(self.case.author.id), } @@ -75,7 +75,7 @@ class PositiveCaseViewTest(TestCase): url = "/cases/positive-cases/" data = { - "case_subject": str(self.case.case_subject.revision_id), + "case_subject_id": str(self.case.case_subject_id), "author": str(self.case.author.id), } @@ -88,7 +88,7 @@ class PositiveCaseViewTest(TestCase): url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" data = { - "case_subject": str(self.case_subject.revision_id), + "case_subject_id": str(self.case.case_subject_id), "outcome": self.case.outcome, "author": str(self.case.author.id), } @@ -123,7 +123,7 @@ class PositiveCaseViewTest(TestCase): url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" data = { - "case_subject": str(self.case_subject.revision_id), + "case_subject_id": str(self.case.case_subject_id), "author": str(self.case.author.id), } @@ -152,5 +152,5 @@ class PositiveCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - PositiveCase.objects.get(revision_id=self.case.revision_id) + PositiveCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() ) -- GitLab From 5cf13e8cb2891f58c4a11ee819239c819dc4f379 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 19:42:46 +0700 Subject: [PATCH 08/11] [RED] Add field change validator on case edit --- apps/cases/tests/test_units/test_investigation_cases.py | 3 ++- apps/cases/tests/test_units/test_monitoring_cases.py | 1 + apps/cases/tests/test_units/test_positive_cases.py | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/cases/tests/test_units/test_investigation_cases.py b/apps/cases/tests/test_units/test_investigation_cases.py index 2e34e6d..4064083 100644 --- a/apps/cases/tests/test_units/test_investigation_cases.py +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -120,7 +120,7 @@ class InvestigationCaseViewTest(TestCase): "case_relation": self.case.case_relation, "medical_symptoms": self.case.medical_symptoms, "risk_factors": self.case.risk_factors, - "is_referral_needed": self.case.is_referral_needed, + "is_referral_needed": not self.case.is_referral_needed, "medical_facility_reference": self.case.medical_facility_reference, "author": str(self.case.author.id), } @@ -135,6 +135,7 @@ class InvestigationCaseViewTest(TestCase): path=url, data=data, content_type="application/json", format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["is_referral_needed"], not self.case.is_referral_needed) current_investigation_case_revision_count = ( InvestigationCase.objects.all().count() diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py index fa4d8c0..fafebb0 100644 --- a/apps/cases/tests/test_units/test_monitoring_cases.py +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -105,6 +105,7 @@ class MonitoringCaseViewTest(TestCase): path=url, data=data, content_type="application/json", format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["outcome"], "Worsened") current_monitoring_case_revision_count = MonitoringCase.objects.all().count() current_active_monitoring_case_revision_count = ( diff --git a/apps/cases/tests/test_units/test_positive_cases.py b/apps/cases/tests/test_units/test_positive_cases.py index fd292c7..3d2400a 100644 --- a/apps/cases/tests/test_units/test_positive_cases.py +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -89,7 +89,7 @@ class PositiveCaseViewTest(TestCase): data = { "case_subject_id": str(self.case.case_subject_id), - "outcome": self.case.outcome, + "outcome": "Negative", "author": str(self.case.author.id), } prev_all_positive_case_revision_count = PositiveCase.objects.all().count() @@ -101,6 +101,7 @@ class PositiveCaseViewTest(TestCase): path=url, data=data, content_type="application/json", format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["outcome"], "Negative") current_positive_case_revision_count = PositiveCase.objects.all().count() current_active_positive_case_revision_count = ( -- GitLab From 2e2f825b1a7a4f20cb945dbd615da1b66b286e31 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 21:44:13 +0700 Subject: [PATCH 09/11] [RED] Added test to prevent access to inactive/deleted revision --- .../tests/test_units/test_case_subjects.py | 30 +++++++----- .../test_units/test_investigation_cases.py | 47 ++++++++++++++----- .../tests/test_units/test_monitoring_cases.py | 39 +++++++++++---- .../tests/test_units/test_positive_cases.py | 37 +++++++++++---- 4 files changed, 110 insertions(+), 43 deletions(-) diff --git a/apps/cases/tests/test_units/test_case_subjects.py b/apps/cases/tests/test_units/test_case_subjects.py index f3eb2f6..a0a4ee5 100644 --- a/apps/cases/tests/test_units/test_case_subjects.py +++ b/apps/cases/tests/test_units/test_case_subjects.py @@ -15,9 +15,6 @@ class CaseSubjectViewTest(TestCase): is_active=True, deleted_at=datetime.now(tz=pytz.UTC) ) self.case_subject_3 = CaseSubjectFactory(is_active=False) - self.case_subject_4 = CaseSubjectFactory( - is_active=False, deleted_at=datetime.now(tz=pytz.UTC) - ) def test_list_case_subjects_only_shows_active_entries(self): url = "/cases/case-subjects/" @@ -34,7 +31,7 @@ class CaseSubjectViewTest(TestCase): "address": self.case_subject_1.address, "district": self.case_subject_1.district, "sub_district": self.case_subject_1.sub_district, - "is_active" : self.case_subject_1.is_active, + "is_active": self.case_subject_1.is_active, }, ] self.assertJSONEqual(json.dumps(response.data), data) @@ -53,21 +50,17 @@ class CaseSubjectViewTest(TestCase): "address": self.case_subject_1.address, "district": self.case_subject_1.district, "sub_district": self.case_subject_1.sub_district, - "is_active" : self.case_subject_1.is_active, + "is_active": self.case_subject_1.is_active, } self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertJSONEqual(json.dumps(response.data), data) - def test_retrieve_case_subject_fails_on_deleted_or_inactive_subject(self): + def test_retrieve_case_subject_fails_on_deleted_subject(self): url = "/cases/case-subjects/" + str(self.case_subject_2.revision_id) + "/" response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - url = "/cases/case-subjects/" + str(self.case_subject_3.revision_id) + "/" - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_create_new_case_subject_success(self): url = "/cases/case-subjects/" @@ -116,7 +109,7 @@ class CaseSubjectViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_edit_account_success(self): + def test_edit_case_subject_success(self): url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" data = { @@ -155,7 +148,7 @@ class CaseSubjectViewTest(TestCase): prev_active_case_subject_revision_count, ) - def test_edit_account_fails_with_incomplete_data(self): + def test_edit_case_subject_fails_with_incomplete_data(self): url = "/cases/case-subjects/" + str(self.case_subject_1.revision_id) + "/" data = { @@ -187,7 +180,9 @@ class CaseSubjectViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - CaseSubject.objects.all(with_deleted=True).filter(revision_id=self.case_subject_1.revision_id).first() + CaseSubject.objects.all(with_deleted=True) + .filter(revision_id=self.case_subject_1.revision_id) + .first() ) current_deleted_all_count = CaseSubject.objects.all(with_deleted=True).count() @@ -195,3 +190,12 @@ class CaseSubjectViewTest(TestCase): self.assertEqual(current_deleted_all_count, prev_deleted_all_count) self.assertEqual(current_deleted_count, prev_deleted_count + 1) + + def test_delete_inactive_case_subject_fails(self): + url = ( + "/cases/case-subjects/" + + str(self.case_subject_3.revision_id) + + "/" + ) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/tests/test_units/test_investigation_cases.py b/apps/cases/tests/test_units/test_investigation_cases.py index 4064083..30d4ad0 100644 --- a/apps/cases/tests/test_units/test_investigation_cases.py +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -52,15 +52,7 @@ class InvestigationCaseViewTest(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_retrieve_investigation_case_fails_on_deleted_or_inactive_subject(self): - url = ( - "/cases/investigation-cases/" - + str(self.other_inactive_case.revision_id) - + "/" - ) - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - + def test_retrieve_investigation_case_fails_on_deleted_subject(self): url = ( "/cases/investigation-cases/" + str(self.other_deleted_case.revision_id) @@ -135,7 +127,9 @@ class InvestigationCaseViewTest(TestCase): path=url, data=data, content_type="application/json", format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["is_referral_needed"], not self.case.is_referral_needed) + self.assertEqual( + response.data["is_referral_needed"], not self.case.is_referral_needed + ) current_investigation_case_revision_count = ( InvestigationCase.objects.all().count() @@ -155,8 +149,26 @@ class InvestigationCaseViewTest(TestCase): current_active_investigation_case_revision_count, prev_active_investigation_case_revision_count, ) + + def test_edit_inactive_investigation_case_fails(self): + url = "/cases/investigation-cases/" + str(self.other_inactive_case.revision_id) + "/" + data = { + "case_subject_id": str(self.other_inactive_case.case_subject_id), + "reference_case_id": str(self.reference_case.case_id), + "case_relation": self.other_inactive_case.case_relation, + "medical_symptoms": self.other_inactive_case.medical_symptoms, + "risk_factors": self.other_inactive_case.risk_factors, + "is_referral_needed": not self.other_inactive_case.is_referral_needed, + "medical_facility_reference": self.other_inactive_case.medical_facility_reference, + "author": str(self.other_inactive_case.author.id), + } + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + - def test_edit_investigation_fails_with_incomplete_data(self): + def test_edit_investigation_case_fails_with_incomplete_data(self): url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" data = { @@ -196,7 +208,9 @@ class InvestigationCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - InvestigationCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() + InvestigationCase.objects.all(with_deleted=True) + .filter(revision_id=self.case.revision_id) + .first() ) current_deleted_all_count = InvestigationCase.objects.all( @@ -206,3 +220,12 @@ class InvestigationCaseViewTest(TestCase): self.assertEqual(current_deleted_all_count, prev_deleted_all_count) self.assertEqual(current_deleted_count, prev_deleted_count + 1) + + def test_delete_inactive_investigation_case_fails(self): + url = ( + "/cases/investigation-cases/" + + str(self.other_inactive_case.revision_id) + + "/" + ) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py index fafebb0..837de21 100644 --- a/apps/cases/tests/test_units/test_monitoring_cases.py +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -41,6 +41,7 @@ class MonitoringCaseViewTest(TestCase): author=self.author, positive_case_id=self.positive_case.case_id, is_active=False, ) + def test_list_monitoring_cases_success(self): url = "/cases/monitoring-cases/" @@ -53,13 +54,7 @@ class MonitoringCaseViewTest(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_retrieve_monitoring_case_fails_on_deleted_or_inactive_subject(self): - url = ( - "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" - ) - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - + def test_retrieve_monitoring_case_fails_on_deleted_subject(self): url = ( "/cases/monitoring-cases/" + str(self.other_deleted_case.revision_id) + "/" ) @@ -88,7 +83,7 @@ class MonitoringCaseViewTest(TestCase): self.assertEqual(case_current_active_count, case_prev_active_count + 1) self.assertEqual(case_current_all_count, case_prev_all_count + 1) - def test_edit_investigation_success(self): + def test_edit_monitoring_case_success(self): url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" data = { @@ -123,8 +118,21 @@ class MonitoringCaseViewTest(TestCase): current_active_monitoring_case_revision_count, prev_active_monitoring_case_revision_count, ) + + def test_edit_inactive_monitoring_case_fails(self): + url = "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" + + data = { + "positive_case_id": str(self.positive_case.revision_id), + "outcome": "Worsened", + "author": str(self.case.author.id), + } + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_monitoring_cannot_have_more_than_one_reference(self): + def test_monitoring_case_cannot_have_more_than_one_reference(self): url = "/cases/monitoring-cases/" data = { "positive_case_id": str(self.positive_case.revision_id), @@ -145,5 +153,16 @@ class MonitoringCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - MonitoringCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() + MonitoringCase.objects.all(with_deleted=True) + .filter(revision_id=self.case.revision_id) + .first() ) + + def test_delete_inactive_monitoring_case_fails(self): + url = ( + "/cases/monitoring-cases/" + + str(self.other_inactive_case.revision_id) + + "/" + ) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/tests/test_units/test_positive_cases.py b/apps/cases/tests/test_units/test_positive_cases.py index 3d2400a..c4b0d11 100644 --- a/apps/cases/tests/test_units/test_positive_cases.py +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -40,11 +40,7 @@ class PositiveCaseViewTest(TestCase): response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_retrieve_positive_case_fails_on_deleted_or_inactive_subject(self): - url = "/cases/positive-cases/" + str(self.other_inactive_case.revision_id) + "/" - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - + def test_retrieve_positive_case_fails_on_deleted_subject(self): url = "/cases/positive-cases/" + str(self.other_deleted_case.revision_id) + "/" response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) @@ -84,7 +80,7 @@ class PositiveCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_edit_positive_success(self): + def test_edit_positive_case_success(self): url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" data = { @@ -120,7 +116,21 @@ class PositiveCaseViewTest(TestCase): prev_active_positive_case_revision_count, ) - def test_edit_positive_fails_with_incomplete_data(self): + def test_edit_inactive_positive_case_fails(self): + url = "/cases/positive-cases/" + str(self.other_inactive_case.revision_id) + "/" + + data = { + "case_subject_id": str(self.case.case_subject_id), + "outcome": "Negative", + "author": str(self.case.author.id), + } + response = self.client.put( + path=url, data=data, content_type="application/json", format="json", + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + + def test_edit_positive_case_fails_with_incomplete_data(self): url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" data = { @@ -153,5 +163,16 @@ class PositiveCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( - PositiveCase.objects.all(with_deleted=True).filter(revision_id=self.case.revision_id).first() + PositiveCase.objects.all(with_deleted=True) + .filter(revision_id=self.case.revision_id) + .first() ) + + def test_delete_inactive_positive_case_fails(self): + url = ( + "/cases/positive-cases/" + + str(self.other_inactive_case.revision_id) + + "/" + ) + response = self.client.delete(url) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) -- GitLab From 2eaa26794dbe03814a12b4d3d0c9965f5b7e8bf5 Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 22:04:47 +0700 Subject: [PATCH 10/11] [RED] Added test monitoring page with different reference --- .../tests/test_units/test_monitoring_cases.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py index 837de21..faba464 100644 --- a/apps/cases/tests/test_units/test_monitoring_cases.py +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -24,13 +24,16 @@ class MonitoringCaseViewTest(TestCase): self.positive_case = PositiveCaseFactory( author=self.author, case_subject_id=self.case_subject.subject_id, ) - self.case = MonitoringCaseFactory( + self.case_1 = MonitoringCaseFactory( author=self.author, positive_case_id=self.positive_case.case_id, ) self.investigation_case = InvestigationCaseFactory( author=self.author, case_subject_id=self.case_subject.subject_id, - reference_case_id=self.positive_case.case_id, + reference_case_id=None, + ) + self.case_2 = MonitoringCaseFactory( + author=self.author, investigation_case_id=self.investigation_case.case_id, ) self.other_deleted_case = MonitoringCaseFactory( author=self.author, @@ -49,7 +52,7 @@ class MonitoringCaseViewTest(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_retrieve_monitoring_case_success(self): - url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + url = "/cases/monitoring-cases/" + str(self.case_2.revision_id) + "/" response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -70,7 +73,7 @@ class MonitoringCaseViewTest(TestCase): data = { "positive_case_id": str(self.positive_case.case_id), "outcome": "Healed", - "author": str(self.case.author.id), + "author": str(self.case_1.author.id), } response = self.client.post( @@ -84,12 +87,12 @@ class MonitoringCaseViewTest(TestCase): self.assertEqual(case_current_all_count, case_prev_all_count + 1) def test_edit_monitoring_case_success(self): - url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + url = "/cases/monitoring-cases/" + str(self.case_1.revision_id) + "/" data = { "positive_case_id": str(self.positive_case.revision_id), "outcome": "Worsened", - "author": str(self.case.author.id), + "author": str(self.case_1.author.id), } prev_all_monitoring_case_revision_count = MonitoringCase.objects.all().count() prev_active_monitoring_case_revision_count = ( @@ -108,7 +111,7 @@ class MonitoringCaseViewTest(TestCase): ) self.assertFalse( - MonitoringCase.objects.get(revision_id=self.case.revision_id).is_active + MonitoringCase.objects.get(revision_id=self.case_1.revision_id).is_active ) self.assertEqual( current_monitoring_case_revision_count, @@ -125,7 +128,7 @@ class MonitoringCaseViewTest(TestCase): data = { "positive_case_id": str(self.positive_case.revision_id), "outcome": "Worsened", - "author": str(self.case.author.id), + "author": str(self.case_1.author.id), } response = self.client.put( path=url, data=data, content_type="application/json", format="json", @@ -138,7 +141,7 @@ class MonitoringCaseViewTest(TestCase): "positive_case_id": str(self.positive_case.revision_id), "investigation_case_id": str(self.investigation_case.revision_id), "outcome": "Healed", - "author": str(self.case.author.id), + "author": str(self.case_1.author.id), } with self.assertRaises(ValidationError): @@ -147,14 +150,14 @@ class MonitoringCaseViewTest(TestCase): ) def test_soft_delete_monitoring_case_success(self): - url = "/cases/monitoring-cases/" + str(self.case.revision_id) + "/" + url = "/cases/monitoring-cases/" + str(self.case_1.revision_id) + "/" response = self.client.delete( path=url, content_type="application/json", format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsNotNone( MonitoringCase.objects.all(with_deleted=True) - .filter(revision_id=self.case.revision_id) + .filter(revision_id=self.case_1.revision_id) .first() ) -- GitLab From c9e067772f0d7c1cec289b741e3fb1c30691588a Mon Sep 17 00:00:00 2001 From: "jonathan.christopher" Date: Wed, 4 Mar 2020 22:49:47 +0700 Subject: [PATCH 11/11] [GREEN] Modify new endpoints to refer to main id instead of revision id --- apps/cases/migrations/0001_initial.py | 124 +++++++----------- apps/cases/models.py | 76 ++++++----- apps/cases/serializers.py | 109 +++++++++++---- .../tests/test_units/test_case_subjects.py | 8 +- .../test_units/test_investigation_cases.py | 11 +- .../tests/test_units/test_monitoring_cases.py | 17 +-- .../tests/test_units/test_positive_cases.py | 13 +- apps/cases/views.py | 39 ++---- apps/commons/managers.py | 14 +- project/settings.py | 2 +- 10 files changed, 221 insertions(+), 192 deletions(-) diff --git a/apps/cases/migrations/0001_initial.py b/apps/cases/migrations/0001_initial.py index fb5b76c..092ed45 100644 --- a/apps/cases/migrations/0001_initial.py +++ b/apps/cases/migrations/0001_initial.py @@ -1,3 +1,5 @@ +# Generated by Django 3.0.1 on 2020-03-04 22:17 + from django.db import migrations, models import django.db.models.deletion import uuid @@ -38,54 +40,7 @@ class Migration(migrations.Migration): options={ "verbose_name_plural": "case subjects", "db_table": "case_subject", - }, - ), - migrations.CreateModel( - name="InvestigationCase", - fields=[ - ( - "revision_id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("case_id", models.UUIDField(default=uuid.uuid4)), - ("case_relation", models.CharField(blank=True, max_length=128)), - ("medical_symptoms", models.TextField(default="{}")), - ("risk_factors", models.TextField(default="{}")), - ("is_referral_needed", models.BooleanField(db_index=True)), - ( - "medical_facility_reference", - models.CharField(blank=True, max_length=128), - ), - ("deleted_at", models.DateTimeField(blank=True, null=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("is_active", models.BooleanField(db_index=True, default=True)), - ( - "author", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.DO_NOTHING, - related_name="investigation_case", - to="accounts.Account", - ), - ), - ( - "case_subject", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="investigation_case", - to="cases.CaseSubject", - ), - ), - ], - options={ - "verbose_name_plural": "investigation cases", - "db_table": "investigation_case", + "ordering": ["-created_at"], }, ), migrations.CreateModel( @@ -101,6 +56,7 @@ class Migration(migrations.Migration): ), ), ("case_id", models.UUIDField(default=uuid.uuid4)), + ("case_subject_id", models.UUIDField(blank=True, null=True)), ("outcome", models.CharField(max_length=256)), ("deleted_at", models.DateTimeField(blank=True, null=True)), ("created_at", models.DateTimeField(auto_now_add=True)), @@ -115,18 +71,11 @@ class Migration(migrations.Migration): to="accounts.Account", ), ), - ( - "case_subject", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="positive_case", - to="cases.CaseSubject", - ), - ), ], options={ "verbose_name_plural": "positive cases", "db_table": "positive_case", + "ordering": ["-created_at"], }, ), migrations.CreateModel( @@ -142,6 +91,8 @@ class Migration(migrations.Migration): ), ), ("case_id", models.UUIDField(default=uuid.uuid4)), + ("investigation_case_id", models.UUIDField(blank=True, null=True)), + ("positive_case_id", models.UUIDField(blank=True, null=True)), ( "is_referred", models.BooleanField(blank=True, db_index=True, null=True), @@ -171,41 +122,54 @@ class Migration(migrations.Migration): to="accounts.Account", ), ), + ], + options={ + "verbose_name_plural": "monitoring cases", + "db_table": "monitoring_case", + "ordering": ["-created_at"], + }, + ), + migrations.CreateModel( + name="InvestigationCase", + fields=[ ( - "investigation_case", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="investigation_monitoring_case", - to="cases.InvestigationCase", + "revision_id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, ), ), + ("case_id", models.UUIDField(default=uuid.uuid4)), + ("case_subject_id", models.UUIDField(blank=True, null=True)), + ("reference_case_id", models.UUIDField(blank=True, null=True)), + ("case_relation", models.CharField(blank=True, max_length=128)), + ("medical_symptoms", models.TextField(default="{}")), + ("risk_factors", models.TextField(default="{}")), + ("is_referral_needed", models.BooleanField(db_index=True)), + ( + "medical_facility_reference", + models.CharField(blank=True, max_length=128), + ), + ("deleted_at", models.DateTimeField(blank=True, null=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("is_active", models.BooleanField(db_index=True, default=True)), ( - "positive_case", + "author", models.ForeignKey( blank=True, null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="positive_monitoring_case", - to="cases.PositiveCase", + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="investigation_case", + to="accounts.Account", ), ), ], options={ - "verbose_name_plural": "monitoring cases", - "db_table": "monitoring_case", + "verbose_name_plural": "investigation cases", + "db_table": "investigation_case", + "ordering": ["-created_at"], }, ), - migrations.AddField( - model_name="investigationcase", - name="reference_case", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="investigation_case", - to="cases.PositiveCase", - ), - ), ] diff --git a/apps/cases/models.py b/apps/cases/models.py index 8e42cdc..c955280 100644 --- a/apps/cases/models.py +++ b/apps/cases/models.py @@ -26,20 +26,19 @@ class CaseSubject(models.Model): class Meta: db_table = "case_subject" verbose_name_plural = "case subjects" + ordering = ["-created_at"] def delete(self): if self.deleted_at is None: self.is_active = False - self.deleted_at = datetime.now(tz=pytz.UTC) + self.deleted_at = datetime.now(tz=pytz.timezone("Asia/Jakarta")) self.save() class PositiveCase(models.Model): revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) case_id = models.UUIDField(default=uuid.uuid4) - case_subject = models.ForeignKey( - CaseSubject, on_delete=models.CASCADE, related_name="positive_case" - ) + case_subject_id = models.UUIDField(blank=True, null=True) outcome = models.CharField(max_length=256) author = models.ForeignKey( Account, @@ -57,27 +56,26 @@ class PositiveCase(models.Model): class Meta: db_table = "positive_case" verbose_name_plural = "positive cases" + ordering = ["-created_at"] def delete(self): if self.deleted_at is None: self.is_active = False - self.deleted_at = datetime.now(tz=pytz.UTC) + self.deleted_at = datetime.now(tz=pytz.timezone("Asia/Jakarta")) self.save() + @property + def case_subject(self): + return CaseSubject.objects.filter( + is_active=True, deleted_at__isnull=True, subject_id=self.case_subject_id + ).first() + class InvestigationCase(models.Model): revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) case_id = models.UUIDField(default=uuid.uuid4) - case_subject = models.ForeignKey( - CaseSubject, on_delete=models.CASCADE, related_name="investigation_case" - ) - reference_case = models.ForeignKey( - PositiveCase, - blank=True, - null=True, - on_delete=models.CASCADE, - related_name="investigation_case", - ) + case_subject_id = models.UUIDField(blank=True, null=True) + reference_case_id = models.UUIDField(blank=True, null=True) case_relation = models.CharField(max_length=128, blank=True) medical_symptoms = models.TextField(default="{}") risk_factors = models.TextField(default="{}") @@ -99,31 +97,32 @@ class InvestigationCase(models.Model): class Meta: db_table = "investigation_case" verbose_name_plural = "investigation cases" + ordering = ["-created_at"] def delete(self): if self.deleted_at is None: self.is_active = False - self.deleted_at = datetime.now(tz=pytz.UTC) + self.deleted_at = datetime.now(tz=pytz.timezone("Asia/Jakarta")) self.save() + @property + def reference_case(self): + return PositiveCase.objects.filter( + is_active=True, deleted_at__isnull=True, case_id=self.reference_case_id + ).first() + + @property + def case_subject(self): + return CaseSubject.objects.filter( + is_active=True, deleted_at__isnull=True, subject_id=self.case_subject_id + ).first() + class MonitoringCase(models.Model): revision_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) case_id = models.UUIDField(default=uuid.uuid4) - investigation_case = models.ForeignKey( - InvestigationCase, - blank=True, - null=True, - on_delete=models.CASCADE, - related_name="investigation_monitoring_case", - ) - positive_case = models.ForeignKey( - PositiveCase, - blank=True, - null=True, - on_delete=models.CASCADE, - related_name="positive_monitoring_case", - ) + investigation_case_id = models.UUIDField(blank=True, null=True) + positive_case_id = models.UUIDField(blank=True, null=True) is_referred = models.BooleanField(blank=True, null=True, db_index=True) is_checked = models.BooleanField(blank=True, null=True, db_index=True) checking_date = models.DateField(blank=True, null=True) @@ -147,6 +146,7 @@ class MonitoringCase(models.Model): class Meta: db_table = "monitoring_case" verbose_name_plural = "monitoring cases" + ordering = ["-created_at"] def save(self, *args, **kwargs): self.clean() @@ -155,7 +155,7 @@ class MonitoringCase(models.Model): def clean(self): super(MonitoringCase, self).clean() - reference_cases = [self.positive_case, self.investigation_case] + reference_cases = [self.positive_case_id, self.investigation_case_id] if sum([case is not None for case in reference_cases]) > 1: raise ValidationError( "Monitoring Case should refer to only 1 type of case (Investigation|Positive)" @@ -164,5 +164,17 @@ class MonitoringCase(models.Model): def delete(self): if self.deleted_at is None: self.is_active = False - self.deleted_at = datetime.now(tz=pytz.UTC) + self.deleted_at = datetime.now(tz=pytz.timezone("Asia/Jakarta")) self.save() + + @property + def investigation_case(self): + return InvestigationCase.objects.filter( + is_active=True, deleted_at__isnull=True, case_id=self.investigation_case_id + ).first() + + @property + def positive_case(self): + return PositiveCase.objects.filter( + is_active=True, deleted_at__isnull=True, case_id=self.positive_case_id + ).first() diff --git a/apps/cases/serializers.py b/apps/cases/serializers.py index 1fc6920..f671a6b 100644 --- a/apps/cases/serializers.py +++ b/apps/cases/serializers.py @@ -8,6 +8,8 @@ from apps.cases.models import ( class CaseSubjectSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) + class Meta: model = CaseSubject fields = [ @@ -19,29 +21,71 @@ class CaseSubjectSerializer(serializers.ModelSerializer): "address", "district", "sub_district", + "is_active", ] -class InvestigationCaseSummarySerializer(serializers.ModelSerializer): - case_subject = CaseSubjectSerializer(read_only=True) +class PositiveCaseSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) class Meta: - model = InvestigationCase + model = PositiveCase + fields = [ + "revision_id", + "case_id", + "case_subject_id", + "outcome", + "author", + "is_active", + ] + + +class PositiveCaseSummarySerializer(serializers.ModelSerializer): + case_subject = serializers.SerializerMethodField() + is_active = serializers.BooleanField(read_only=True) + + class Meta: + model = PositiveCase fields = [ "revision_id", "case_id", "case_subject", - "reference_case", + "outcome", + "author", + "is_active", + ] + + def get_case_subject(self, instance): + case_subject = instance.case_subject + if case_subject: + return CaseSubjectSerializer(case_subject).data + + +class InvestigationCaseSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) + + class Meta: + model = InvestigationCase + fields = [ + "revision_id", + "case_id", + "case_subject_id", + "reference_case_id", "case_relation", "medical_symptoms", "risk_factors", "is_referral_needed", "medical_facility_reference", "author", + "is_active", ] -class InvestigationCaseSerializer(serializers.ModelSerializer): +class InvestigationCaseSummarySerializer(serializers.ModelSerializer): + case_subject = serializers.SerializerMethodField() + reference_case = serializers.SerializerMethodField() + is_active = serializers.BooleanField(read_only=True) + class Meta: model = InvestigationCase fields = [ @@ -55,17 +99,30 @@ class InvestigationCaseSerializer(serializers.ModelSerializer): "is_referral_needed", "medical_facility_reference", "author", + "is_active", ] + def get_case_subject(self, instance): + case_subject = instance.case_subject + if case_subject: + return CaseSubjectSerializer(case_subject).data + + def get_reference_case(self, instance): + reference_case = instance.reference_case + if reference_case: + return PositiveCaseSerializer(reference_case).data + + +class MonitoringCaseSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) -class MonitoringCaseSummarySerializer(serializers.ModelSerializer): class Meta: model = MonitoringCase fields = [ "revision_id", "case_id", - "investigation_case", - "positive_case", + "investigation_case_id", + "positive_case_id", "is_referred", "is_checked", "is_regularly_treated", @@ -73,30 +130,38 @@ class MonitoringCaseSummarySerializer(serializers.ModelSerializer): "treatment_end_date", "outcome", "author", + "is_active", ] -class PositiveCaseSummarySerializer(serializers.ModelSerializer): - case_subject = CaseSubjectSerializer(read_only=True) +class MonitoringCaseSummarySerializer(serializers.ModelSerializer): + investigation_case = serializers.SerializerMethodField() + positive_case = serializers.SerializerMethodField() + is_active = serializers.BooleanField(read_only=True) class Meta: - model = PositiveCase + model = MonitoringCase fields = [ "revision_id", "case_id", - "case_subject", + "investigation_case", + "positive_case", + "is_referred", + "is_checked", + "is_regularly_treated", + "treatment_start_date", + "treatment_end_date", "outcome", "author", + "is_active", ] + def get_investigation_case(self, instance): + investigation_case = instance.investigation_case + if investigation_case: + return InvestigationCaseSerializer(investigation_case).data -class PositiveCaseSerializer(serializers.ModelSerializer): - class Meta: - model = PositiveCase - fields = [ - "revision_id", - "case_id", - "case_subject", - "outcome", - "author", - ] + def get_positive_case(self, instance): + positive_case = instance.positive_case + if positive_case: + return PositiveCaseSerializer(positive_case).data diff --git a/apps/cases/tests/test_units/test_case_subjects.py b/apps/cases/tests/test_units/test_case_subjects.py index a0a4ee5..640fb37 100644 --- a/apps/cases/tests/test_units/test_case_subjects.py +++ b/apps/cases/tests/test_units/test_case_subjects.py @@ -12,7 +12,7 @@ class CaseSubjectViewTest(TestCase): def setUp(self): self.case_subject_1 = CaseSubjectFactory(is_active=True) self.case_subject_2 = CaseSubjectFactory( - is_active=True, deleted_at=datetime.now(tz=pytz.UTC) + is_active=True, deleted_at=datetime.now(tz=pytz.timezone("Asia/Jakarta")) ) self.case_subject_3 = CaseSubjectFactory(is_active=False) @@ -192,10 +192,6 @@ class CaseSubjectViewTest(TestCase): self.assertEqual(current_deleted_count, prev_deleted_count + 1) def test_delete_inactive_case_subject_fails(self): - url = ( - "/cases/case-subjects/" - + str(self.case_subject_3.revision_id) - + "/" - ) + url = "/cases/case-subjects/" + str(self.case_subject_3.revision_id) + "/" response = self.client.delete(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/tests/test_units/test_investigation_cases.py b/apps/cases/tests/test_units/test_investigation_cases.py index 30d4ad0..a550ca2 100644 --- a/apps/cases/tests/test_units/test_investigation_cases.py +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -31,7 +31,7 @@ class InvestigationCaseViewTest(TestCase): author=self.author, case_subject_id=self.case_subject.subject_id, reference_case_id=self.reference_case.case_id, - deleted_at=datetime.now(tz=pytz.UTC), + deleted_at=datetime.now(tz=pytz.timezone("Asia/Jakarta")), ) self.other_inactive_case = InvestigationCaseFactory( author=self.author, @@ -149,9 +149,13 @@ class InvestigationCaseViewTest(TestCase): current_active_investigation_case_revision_count, prev_active_investigation_case_revision_count, ) - + def test_edit_inactive_investigation_case_fails(self): - url = "/cases/investigation-cases/" + str(self.other_inactive_case.revision_id) + "/" + url = ( + "/cases/investigation-cases/" + + str(self.other_inactive_case.revision_id) + + "/" + ) data = { "case_subject_id": str(self.other_inactive_case.case_subject_id), "reference_case_id": str(self.reference_case.case_id), @@ -167,7 +171,6 @@ class InvestigationCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_edit_investigation_case_fails_with_incomplete_data(self): url = "/cases/investigation-cases/" + str(self.case.revision_id) + "/" diff --git a/apps/cases/tests/test_units/test_monitoring_cases.py b/apps/cases/tests/test_units/test_monitoring_cases.py index faba464..274122f 100644 --- a/apps/cases/tests/test_units/test_monitoring_cases.py +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -38,13 +38,14 @@ class MonitoringCaseViewTest(TestCase): self.other_deleted_case = MonitoringCaseFactory( author=self.author, positive_case_id=self.positive_case.case_id, - deleted_at=datetime.now(tz=pytz.UTC), + deleted_at=datetime.now(tz=pytz.timezone("Asia/Jakarta")), ) self.other_inactive_case = MonitoringCaseFactory( - author=self.author, positive_case_id=self.positive_case.case_id, is_active=False, + author=self.author, + positive_case_id=self.positive_case.case_id, + is_active=False, ) - def test_list_monitoring_cases_success(self): url = "/cases/monitoring-cases/" @@ -121,9 +122,11 @@ class MonitoringCaseViewTest(TestCase): current_active_monitoring_case_revision_count, prev_active_monitoring_case_revision_count, ) - + def test_edit_inactive_monitoring_case_fails(self): - url = "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" + url = ( + "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" + ) data = { "positive_case_id": str(self.positive_case.revision_id), @@ -163,9 +166,7 @@ class MonitoringCaseViewTest(TestCase): def test_delete_inactive_monitoring_case_fails(self): url = ( - "/cases/monitoring-cases/" - + str(self.other_inactive_case.revision_id) - + "/" + "/cases/monitoring-cases/" + str(self.other_inactive_case.revision_id) + "/" ) response = self.client.delete(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/tests/test_units/test_positive_cases.py b/apps/cases/tests/test_units/test_positive_cases.py index c4b0d11..0700854 100644 --- a/apps/cases/tests/test_units/test_positive_cases.py +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -22,10 +22,12 @@ class PositiveCaseViewTest(TestCase): self.other_deleted_case = PositiveCaseFactory( author=self.author, case_subject_id=self.case_subject.subject_id, - deleted_at=datetime.now(tz=pytz.UTC), + deleted_at=datetime.now(tz=pytz.timezone("Asia/Jakarta")), ) self.other_inactive_case = PositiveCaseFactory( - author=self.author, case_subject_id=self.case_subject.subject_id, is_active=False, + author=self.author, + case_subject_id=self.case_subject.subject_id, + is_active=False, ) def test_list_positive_cases_success(self): @@ -129,7 +131,6 @@ class PositiveCaseViewTest(TestCase): ) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_edit_positive_case_fails_with_incomplete_data(self): url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" @@ -169,10 +170,6 @@ class PositiveCaseViewTest(TestCase): ) def test_delete_inactive_positive_case_fails(self): - url = ( - "/cases/positive-cases/" - + str(self.other_inactive_case.revision_id) - + "/" - ) + url = "/cases/positive-cases/" + str(self.other_inactive_case.revision_id) + "/" response = self.client.delete(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/apps/cases/views.py b/apps/cases/views.py index d6f0984..99338aa 100644 --- a/apps/cases/views.py +++ b/apps/cases/views.py @@ -16,18 +16,18 @@ from apps.cases.serializers import ( InvestigationCaseSummarySerializer, InvestigationCaseSerializer, MonitoringCaseSummarySerializer, + MonitoringCaseSerializer, PositiveCaseSummarySerializer, PositiveCaseSerializer, ) class CaseSubjectViewSet(viewsets.ViewSet): - queryset = CaseSubject.objects.active_revisions() serializer_class = CaseSubjectSerializer + queryset = CaseSubject.objects.active_revisions() def list(self, request): - queryset = self.queryset - serializer = self.serializer_class(queryset, many=True) + serializer = self.serializer_class(self.queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): @@ -38,7 +38,7 @@ class CaseSubjectViewSet(viewsets.ViewSet): def create(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - CaseSubject.objects.create(**serializer.validated_data) + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED,) def update(self, request, pk=None): @@ -52,12 +52,10 @@ class CaseSubjectViewSet(viewsets.ViewSet): subject_id=previous_instance.subject_id, is_active=True, ) - previous_instance.is_active = False previous_instance.save() update_serializer = self.serializer_class(instance=new_instance) - return Response(update_serializer.data, status=status.HTTP_200_OK) def destroy(self, request, pk=None): @@ -68,13 +66,10 @@ class CaseSubjectViewSet(viewsets.ViewSet): class InvestigationCaseViewSet(viewsets.ViewSet): - queryset = InvestigationCase.objects.active_revisions().select_related( - "case_subject" - ) + queryset = InvestigationCase.objects.active_revisions() def list(self, request): - queryset = self.queryset - serializer = InvestigationCaseSummarySerializer(queryset, many=True) + serializer = InvestigationCaseSummarySerializer(self.queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): @@ -111,30 +106,26 @@ class InvestigationCaseViewSet(viewsets.ViewSet): class MonitoringCaseViewSet(viewsets.ViewSet): - queryset = MonitoringCase.objects.active_revisions().select_related( - "positive_case", "investigation_case", - ) - serializer_class = MonitoringCaseSummarySerializer + queryset = MonitoringCase.objects.active_revisions() def list(self, request): - queryset = self.queryset - serializer = self.serializer_class(queryset, many=True) + serializer = MonitoringCaseSummarySerializer(self.queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): instance = get_object_or_404(self.queryset, pk=pk) - serializer = self.serializer_class(instance) + serializer = MonitoringCaseSummarySerializer(instance) return Response(serializer.data, status=status.HTTP_200_OK) def create(self, request): - serializer = self.serializer_class(data=request.data) + serializer = MonitoringCaseSerializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED,) def update(self, request, pk=None): previous_instance = get_object_or_404(self.queryset, pk=pk) - serializer = self.serializer_class(previous_instance, data=request.data) + serializer = MonitoringCaseSerializer(previous_instance, data=request.data) serializer.is_valid(raise_exception=True) serializer_data = serializer.validated_data @@ -145,7 +136,7 @@ class MonitoringCaseViewSet(viewsets.ViewSet): previous_instance.is_active = False previous_instance.save() - update_serializer = self.serializer_class(instance=new_instance) + update_serializer = MonitoringCaseSummarySerializer(instance=new_instance) return Response(update_serializer.data, status=status.HTTP_200_OK) def destroy(self, request, pk=None): @@ -156,11 +147,10 @@ class MonitoringCaseViewSet(viewsets.ViewSet): class PositiveCaseViewSet(viewsets.ViewSet): - queryset = PositiveCase.objects.active_revisions().select_related("case_subject") + queryset = PositiveCase.objects.active_revisions() def list(self, request): - queryset = self.queryset - serializer = PositiveCaseSummarySerializer(queryset, many=True) + serializer = PositiveCaseSummarySerializer(self.queryset, many=True) return Response(serializer.data, status=status.HTTP_200_OK) def retrieve(self, request, pk=None): @@ -183,7 +173,6 @@ class PositiveCaseViewSet(viewsets.ViewSet): is_active=True, **serializer.validated_data, ) - previous_instance.is_active = False previous_instance.save() diff --git a/apps/commons/managers.py b/apps/commons/managers.py index d9b778a..9a9d212 100644 --- a/apps/commons/managers.py +++ b/apps/commons/managers.py @@ -2,16 +2,18 @@ from django.db import models class SoftObjectManager(models.Manager): - def all(self, with_deleted=False): - queryset = self.get_queryset() + def get_queryset(self, with_deleted=False): if with_deleted: - return queryset - return queryset.filter(deleted_at__isnull=True) + return super().get_queryset() + return super().get_queryset().filter(deleted_at__isnull=True) + + def all(self, with_deleted=False): + return self.get_queryset(with_deleted=with_deleted) def deleted(self): - queryset = self.get_queryset() + queryset = self.get_queryset(with_deleted=True) return queryset.filter(deleted_at__isnull=False) def active_revisions(self): queryset = self.get_queryset() - return queryset.filter(deleted_at__isnull=True, is_active=True) + return queryset.filter(is_active=True) diff --git a/project/settings.py b/project/settings.py index 5c64c72..1432f0c 100644 --- a/project/settings.py +++ b/project/settings.py @@ -127,7 +127,7 @@ REST_FRAMEWORK = { LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Asia/Jakarta" USE_I18N = True -- GitLab