diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py index c9e390ea66864c52d1d824b211885cb33e214c93..e6e7d224e477463ce3ed8d694935d220a60882d5 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 058490b566e0dccc51f18337d110281f7a66590b..ecdcf056bdfabab97c42648cae326d6be183bb61 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/accounts/serializers.py b/apps/accounts/serializers.py index fbf73f74e72e211e757836f5fb5cbc7c5d987325..18cfbc278ceb37d46d852779c1fdfd03d736d5d8 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/tests/factories/accounts.py b/apps/accounts/tests/factories/accounts.py index 4890b9f7f0d16b83767166b29cf95e0cd5272ca6..f20e27eea668df2bfab7a73c8798861671969247 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/accounts/views.py b/apps/accounts/views.py index c943a17736760e5977f0e22488121f137128ae5e..da6af13a7688da375dcf1fb8dfd1f7b5520b4592 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/__init__.py b/apps/cases/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/cases/admin.py b/apps/cases/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /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 0000000000000000000000000000000000000000..648e0c7f51006c666c3dfcb9584e033ea4158e36 --- /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/0001_initial.py b/apps/cases/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..092ed458a3c1248220cd14dcd95eb62483ac7ca6 --- /dev/null +++ b/apps/cases/migrations/0001_initial.py @@ -0,0 +1,175 @@ +# 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 + + +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", + "ordering": ["-created_at"], + }, + ), + 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)), + ("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)), + ("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", + ), + ), + ], + options={ + "verbose_name_plural": "positive cases", + "db_table": "positive_case", + "ordering": ["-created_at"], + }, + ), + 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)), + ("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), + ), + ( + "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", + ), + ), + ], + options={ + "verbose_name_plural": "monitoring cases", + "db_table": "monitoring_case", + "ordering": ["-created_at"], + }, + ), + 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_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)), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="investigation_case", + to="accounts.Account", + ), + ), + ], + options={ + "verbose_name_plural": "investigation cases", + "db_table": "investigation_case", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/apps/cases/migrations/__init__.py b/apps/cases/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/cases/models.py b/apps/cases/models.py new file mode 100644 index 0000000000000000000000000000000000000000..c95528055ade6e503d193a367ee121ae1a23b888 --- /dev/null +++ b/apps/cases/models.py @@ -0,0 +1,180 @@ +import uuid +import pytz +from datetime import datetime +from django.core.exceptions import ValidationError +from django.db import models + +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" + ordering = ["-created_at"] + + def delete(self): + if self.deleted_at is None: + self.is_active = False + 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_id = models.UUIDField(blank=True, null=True) + 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" + ordering = ["-created_at"] + + def delete(self): + if self.deleted_at is None: + self.is_active = False + 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_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="{}") + 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" + ordering = ["-created_at"] + + def delete(self): + if self.deleted_at is None: + self.is_active = False + 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_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) + 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" + ordering = ["-created_at"] + + 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_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)" + ) + + def delete(self): + if self.deleted_at is None: + self.is_active = False + 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 new file mode 100644 index 0000000000000000000000000000000000000000..f671a6be58c256af8efd5b4c24371776f778cbc5 --- /dev/null +++ b/apps/cases/serializers.py @@ -0,0 +1,167 @@ +from rest_framework import serializers +from apps.cases.models import ( + CaseSubject, + InvestigationCase, + MonitoringCase, + PositiveCase, +) + + +class CaseSubjectSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) + + class Meta: + model = CaseSubject + fields = [ + "revision_id", + "subject_id", + "name", + "age", + "is_male", + "address", + "district", + "sub_district", + "is_active", + ] + + +class PositiveCaseSerializer(serializers.ModelSerializer): + is_active = serializers.BooleanField(read_only=True) + + class Meta: + 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", + "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 InvestigationCaseSummarySerializer(serializers.ModelSerializer): + case_subject = serializers.SerializerMethodField() + reference_case = serializers.SerializerMethodField() + is_active = serializers.BooleanField(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", + "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 Meta: + model = MonitoringCase + fields = [ + "revision_id", + "case_id", + "investigation_case_id", + "positive_case_id", + "is_referred", + "is_checked", + "is_regularly_treated", + "treatment_start_date", + "treatment_end_date", + "outcome", + "author", + "is_active", + ] + + +class MonitoringCaseSummarySerializer(serializers.ModelSerializer): + investigation_case = serializers.SerializerMethodField() + positive_case = serializers.SerializerMethodField() + is_active = serializers.BooleanField(read_only=True) + + 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", + "is_active", + ] + + def get_investigation_case(self, instance): + investigation_case = instance.investigation_case + if investigation_case: + return InvestigationCaseSerializer(investigation_case).data + + 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/__init__.py b/apps/cases/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/cases/tests/factories/__init__.py b/apps/cases/tests/factories/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/apps/cases/tests/factories/case_subjects.py b/apps/cases/tests/factories/case_subjects.py new file mode 100644 index 0000000000000000000000000000000000000000..10f78371edd8cf777b07d1d59b499660cdefc877 --- /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 0000000000000000000000000000000000000000..92bfb0d61a41640e96827984e8928faa5088607b --- /dev/null +++ b/apps/cases/tests/factories/cases.py @@ -0,0 +1,41 @@ +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 + + author = factory.SubFactory(AccountFactory) + outcome = "Positive" + + +class InvestigationCaseFactory(factory.DjangoModelFactory): + class Meta: + model = InvestigationCase + + 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 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 0000000000000000000000000000000000000000..640fb373470c95a63da99e3a3a9f4897528db1fe --- /dev/null +++ b/apps/cases/tests/test_units/test_case_subjects.py @@ -0,0 +1,197 @@ +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.timezone("Asia/Jakarta")) + ) + self.case_subject_3 = CaseSubjectFactory(is_active=False) + + 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, + "is_active": self.case_subject_1.is_active, + }, + ] + 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, + "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_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) + + 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", + ) + response.data["is_active"] = self.case_subject_1.is_active + + 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_case_subject_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") + response.data.pop("is_active") + + 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_case_subject_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.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() + 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) + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..a550ca24742cb1477aa86d818f23740e36abbfd5 --- /dev/null +++ b/apps/cases/tests/test_units/test_investigation_cases.py @@ -0,0 +1,234 @@ +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_id=self.case_subject.subject_id, author=self.author, + ) + self.case = InvestigationCaseFactory( + author=self.author, + 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_id=self.case_subject.subject_id, + reference_case_id=self.reference_case.case_id, + deleted_at=datetime.now(tz=pytz.timezone("Asia/Jakarta")), + ) + self.other_inactive_case = InvestigationCaseFactory( + author=self.author, + case_subject_id=self.case_subject.subject_id, + reference_case_id=self.reference_case.case_id, + is_active=False, + ) + + def test_list_investigation_cases_success(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_subject(self): + 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_id": str(self.reference_case.case_id), + "case_relation": self.case.case_relation, + "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, + "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_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, + "is_referral_needed": not 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) + self.assertEqual( + response.data["is_referral_needed"], not self.case.is_referral_needed + ) + + 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_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_case_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_id": 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.all(with_deleted=True) + .filter(revision_id=self.case.revision_id) + .first() + ) + + 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) + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..274122ff20da815b56607bde48f29e991a7274e2 --- /dev/null +++ b/apps/cases/tests/test_units/test_monitoring_cases.py @@ -0,0 +1,172 @@ +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_id=self.case_subject.subject_id, + ) + 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=None, + ) + self.case_2 = MonitoringCaseFactory( + author=self.author, investigation_case_id=self.investigation_case.case_id, + ) + self.other_deleted_case = MonitoringCaseFactory( + author=self.author, + positive_case_id=self.positive_case.case_id, + 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, + ) + + def test_list_monitoring_cases_success(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_2.revision_id) + "/" + + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_retrieve_monitoring_case_fails_on_deleted_subject(self): + 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_id": str(self.positive_case.case_id), + "outcome": "Healed", + "author": str(self.case_1.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_monitoring_case_success(self): + 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_1.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) + self.assertEqual(response.data["outcome"], "Worsened") + + 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_1.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_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_1.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_case_cannot_have_more_than_one_reference(self): + url = "/cases/monitoring-cases/" + data = { + "positive_case_id": str(self.positive_case.revision_id), + "investigation_case_id": str(self.investigation_case.revision_id), + "outcome": "Healed", + "author": str(self.case_1.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_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_1.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 new file mode 100644 index 0000000000000000000000000000000000000000..07008548d5219e7b8aa036787a4663567e13317e --- /dev/null +++ b/apps/cases/tests/test_units/test_positive_cases.py @@ -0,0 +1,175 @@ +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_id=self.case_subject.subject_id, + ) + self.other_deleted_case = PositiveCaseFactory( + author=self.author, + case_subject_id=self.case_subject.subject_id, + 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, + ) + + def test_list_positive_cases_success(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_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) + + 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_id": str(self.case.case_subject_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_id": str(self.case.case_subject_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_case_success(self): + url = "/cases/positive-cases/" + str(self.case.revision_id) + "/" + + data = { + "case_subject_id": str(self.case.case_subject_id), + "outcome": "Negative", + "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) + self.assertEqual(response.data["outcome"], "Negative") + + 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_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 = { + "case_subject_id": str(self.case.case_subject_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.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) diff --git a/apps/cases/urls.py b/apps/cases/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..7b01ff13f3c4c063855043544f7671a949e130b2 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..99338aabc061b62290e668d1721f34be6fd2f9df --- /dev/null +++ b/apps/cases/views.py @@ -0,0 +1,186 @@ +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 + +from apps.cases.models import ( + CaseSubject, + InvestigationCase, + MonitoringCase, + PositiveCase, +) +from apps.cases.serializers import ( + CaseSubjectSerializer, + InvestigationCaseSummarySerializer, + InvestigationCaseSerializer, + MonitoringCaseSummarySerializer, + MonitoringCaseSerializer, + PositiveCaseSummarySerializer, + PositiveCaseSerializer, +) + + +class CaseSubjectViewSet(viewsets.ViewSet): + serializer_class = CaseSubjectSerializer + queryset = CaseSubject.objects.active_revisions() + + def list(self, request): + serializer = self.serializer_class(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) + 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.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.delete() + serializer = self.serializer_class(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class InvestigationCaseViewSet(viewsets.ViewSet): + queryset = InvestigationCase.objects.active_revisions() + + def list(self, request): + serializer = InvestigationCaseSummarySerializer(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 = InvestigationCaseSummarySerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + 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 = InvestigationCaseSerializer(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 = 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.delete() + serializer = InvestigationCaseSerializer(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class MonitoringCaseViewSet(viewsets.ViewSet): + queryset = MonitoringCase.objects.active_revisions() + + def list(self, request): + 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 = MonitoringCaseSummarySerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + 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 = MonitoringCaseSerializer(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 = MonitoringCaseSummarySerializer(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.delete() + serializer = MonitoringCaseSummarySerializer(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class PositiveCaseViewSet(viewsets.ViewSet): + queryset = PositiveCase.objects.active_revisions() + + def list(self, request): + serializer = PositiveCaseSummarySerializer(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 = PositiveCaseSummarySerializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) + + def create(self, request): + 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 = PositiveCaseSerializer(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 = PositiveCaseSerializer(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.delete() + serializer = PositiveCaseSummarySerializer(instance=instance) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apps/commons/managers.py b/apps/commons/managers.py new file mode 100644 index 0000000000000000000000000000000000000000..9a9d212039d15f64f07d687c84e6524f4e41fa7f --- /dev/null +++ b/apps/commons/managers.py @@ -0,0 +1,19 @@ +from django.db import models + + +class SoftObjectManager(models.Manager): + def get_queryset(self, with_deleted=False): + if with_deleted: + 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(with_deleted=True) + return queryset.filter(deleted_at__isnull=False) + + def active_revisions(self): + queryset = self.get_queryset() + return queryset.filter(is_active=True) diff --git a/project/settings.py b/project/settings.py index 6b28cd5b38f172260e3d5f6ba22d7715310d2f3b..1432f0cd4a31b71cef0b6a4aba57c6b0c58de4fb 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,13 +114,20 @@ 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/ LANGUAGE_CODE = "en-us" -TIME_ZONE = "UTC" +TIME_ZONE = "Asia/Jakarta" USE_I18N = True diff --git a/project/urls.py b/project/urls.py index d2bd5c3d2f309c97cb0c1bc4f4a5c308fa04b6ee..50182b141eb9ae6e49ecff56131bc6d892577cd4 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")), ]