Skip to content

Commit

Permalink
Adds validation to consulation date fields (admission_date, `discha…
Browse files Browse the repository at this point in the history
…rge_date`, `death_datetime`) (#1415)

* validation: disallow admision_date to be future

* validation: death_datetime not future or before admission date

* validation: disallow discharge_date before admission or future

* clean

* add tests, draft

* fix tests

* fix user test

---------

Co-authored-by: Vignesh Hari <vichuhari100@gmail.com>
  • Loading branch information
rithviknishad and vigneshhari authored Jul 5, 2023
1 parent 46d6b35 commit 4163d74
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 17 deletions.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,14 @@
"django": true,
"justMyCode": false
},
{
"name": "Python: Django test",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["test", "--keepdb"],
"django": true,
"justMyCode": false
},
]
}
43 changes: 31 additions & 12 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,17 @@ def validate(self, attrs):
validated["referred_to"] = None
elif validated.get("referred_to"):
validated["referred_to_external"] = None
if (
validated["suggestion"] is SuggestionChoices.A
and validated.get("admitted")
and not validated.get("admission_date")
):
raise ValidationError(
{
"admission_date": [
"This field is required as the patient has been admitted."
]
}
)
if validated["suggestion"] is SuggestionChoices.A:
if not validated.get("admission_date"):
raise ValidationError(
{
"admission_date": "This field is required as the patient has been admitted."
}
)
if validated["admission_date"] > now():
raise ValidationError(
{"admission_date": "This field value cannot be in the future."}
)

if "action" in validated:
if validated["action"] == PatientRegistration.ActionEnum.REVIEW:
Expand Down Expand Up @@ -432,13 +431,33 @@ def validate(self, attrs):
if attrs.get("discharge_reason") == "EXP":
if not attrs.get("death_datetime"):
raise ValidationError({"death_datetime": "This field is required"})
if attrs.get("death_datetime") > now():
raise ValidationError(
{"death_datetime": "This field value cannot be in the future."}
)
if attrs.get("death_datetime") < self.instance.admission_date:
raise ValidationError(
{
"death_datetime": "This field value cannot be before the admission date."
}
)
if not attrs.get("death_confirmed_doctor"):
raise ValidationError(
{"death_confirmed_doctor": "This field is required"}
)
attrs["discharge_date"] = attrs["death_datetime"]
elif not attrs.get("discharge_date"):
raise ValidationError({"discharge_date": "This field is required"})
elif attrs.get("discharge_date") > now():
raise ValidationError(
{"discharge_date": "This field value cannot be in the future."}
)
elif attrs.get("discharge_date") < self.instance.admission_date:
raise ValidationError(
{
"discharge_date": "This field value cannot be before the admission date."
}
)
return attrs

def save(self, **kwargs):
Expand Down
150 changes: 150 additions & 0 deletions care/facility/tests/test_patient_consultation_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import datetime

from django.utils.timezone import make_aware
from rest_framework import status
from rest_framework.test import APIRequestFactory, APITestCase

from care.facility.api.viewsets.patient_consultation import PatientConsultationViewSet
from care.facility.models.patient_consultation import (
CATEGORY_CHOICES,
PatientConsultation,
)
from care.facility.tests.mixins import TestClassMixin
from care.utils.tests.test_base import TestBase


class TestPatientConsultation(TestBase, TestClassMixin, APITestCase):
default_data = {
"symptoms": [1],
"category": CATEGORY_CHOICES[0][0],
"examination_details": "examination_details",
"history_of_present_illness": "history_of_present_illness",
"prescribed_medication": "prescribed_medication",
"suggestion": PatientConsultation.SUGGESTION_CHOICES[0][0],
}

def setUp(self):
self.factory = APIRequestFactory()
self.consultation = self.create_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)

def create_admission_consultation(self, patient=None, **kwargs):
patient = (
self.create_patient(facility_id=self.facility.id)
if not patient
else patient
)
data = self.default_data.copy()
kwargs.update(
{
"patient": patient.external_id,
"facility": self.facility.external_id,
}
)
data.update(kwargs)
res = self.new_request(
(self.get_url(), data, "json"),
{"post": "create"},
PatientConsultationViewSet,
self.state_admin,
{},
)
return PatientConsultation.objects.get(external_id=res.data["id"])

def get_url(self, consultation=None):
if consultation:
return f"/api/v1/consultation/{consultation.external_id}"
return "/api/v1/consultation"

def discharge(self, consultation, **kwargs):
return self.new_request(
(f"{self.get_url(consultation)}/discharge_patient", kwargs, "json"),
{"post": "discharge_patient"},
PatientConsultationViewSet,
self.state_admin,
{"external_id": consultation.external_id},
)

def test_discharge_as_recovered_preadmission(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="REC",
discharge_date="2002-04-01T16:30:00Z",
discharge_notes="Discharge as recovered before admission",
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_discharge_as_recovered_future(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="REC",
discharge_date="2319-04-01T15:30:00Z",
discharge_notes="Discharge as recovered in the future",
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_discharge_as_recovered_after_admission(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="REC",
discharge_date="2020-04-02T15:30:00Z",
discharge_notes="Discharge as recovered after admission before future",
)
self.assertEqual(res.status_code, status.HTTP_200_OK)

def test_discharge_as_expired_pre_admission(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="EXP",
death_datetime="2002-04-01T16:30:00Z",
discharge_notes="Death before admission",
death_confirmed_doctor="Dr. Test",
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_discharge_as_expired_future(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="EXP",
death_datetime="2319-04-01T15:30:00Z",
discharge_notes="Death in the future",
death_confirmed_doctor="Dr. Test",
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

def test_discharge_as_expired_after_admission(self):
consultation = self.create_admission_consultation(
suggestion="A",
admission_date=make_aware(datetime.datetime(2020, 4, 1, 15, 30, 00)),
)
res = self.discharge(
consultation,
discharge_reason="EXP",
death_datetime="2020-04-02T15:30:00Z",
discharge_notes="Death after admission before future",
death_confirmed_doctor="Dr. Test",
discharge_date="2319-04-01T15:30:00Z",
)
self.assertEqual(res.status_code, status.HTTP_200_OK)
5 changes: 3 additions & 2 deletions care/users/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ def setUpClass(cls) -> None:
"""
Runs once per class method
Create 2 users
Create 3 users
- 2 users initialized by setUpClass of TestBase
- 1 will be used to check if they can tinker attributes of the other
"""
super(TestUser, cls).setUpClass()
Expand Down Expand Up @@ -136,7 +137,7 @@ def test_user_can_read_all(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)
res_data_json = response.json()
# test total user count
self.assertEqual(res_data_json["count"], 2) # 2 existing, plus the new one
self.assertEqual(res_data_json["count"], 3) # 3 existing, plus the new one
results = res_data_json["results"]
# test presence of usernames
self.assertIn(self.user.id, {r["id"] for r in results})
Expand Down
10 changes: 7 additions & 3 deletions care/utils/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def create_patient(cls, **kwargs):

patient_data.update(
{
"facility": cls.facility,
"district_id": district_id,
"state_id": state_id,
"disease_status": getattr(
Expand Down Expand Up @@ -218,6 +219,12 @@ def setUpClass(cls) -> None:
cls.super_user = cls.create_super_user(district=cls.district)
cls.facility = cls.create_facility(cls.district)
cls.patient = cls.create_patient()
cls.state_admin = cls.create_user(
cls.district,
username="state-admin",
user_type=User.TYPE_VALUE_MAP["StateAdmin"],
home_facility=cls.facility,
)

cls.user_data = cls.get_user_data(cls.district, cls.user_type)
cls.facility_data = cls.get_facility_data(cls.district)
Expand Down Expand Up @@ -394,13 +401,10 @@ def get_consultation_data(cls):
"prescribed_medication": "prescribed_medication",
"suggestion": PatientConsultation.SUGGESTION_CHOICES[0][0],
"referred_to": None,
"admitted": False,
"admission_date": None,
"discharge_date": None,
"consultation_notes": "",
"course_in_facility": "",
"discharge_advice": {},
"prescriptions": {},
"created_date": mock_equal,
"modified_date": mock_equal,
}
Expand Down

0 comments on commit 4163d74

Please sign in to comment.