From 8cd103251e229925f1efd8f1feb15efa0ff80bff Mon Sep 17 00:00:00 2001 From: Vignesh Hari <14056798+vigneshhari@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:22:27 +0530 Subject: [PATCH] Add list and detail serializer with tests for shifting module #1549 (#2392) * Add list and detail serializer with tests for shifting module * viewset improve select_related for list action * Fix Test Cases * Update care/facility/api/serializers/shifting.py * Fix Linting * Fix Linting and tests --------- Co-authored-by: Suyash Singh Co-authored-by: GeekGawd <89455838+GeekGawd@users.noreply.github.com> Co-authored-by: Aakash Singh --- care/facility/api/serializers/shifting.py | 124 +++++++++- care/facility/api/viewsets/shifting.py | 90 ++++--- .../tests/test_facility_shifting_api.py | 222 ++++++++++++++++++ care/utils/tests/test_utils.py | 81 +++++++ 4 files changed, 482 insertions(+), 35 deletions(-) create mode 100644 care/facility/tests/test_facility_shifting_api.py diff --git a/care/facility/api/serializers/shifting.py b/care/facility/api/serializers/shifting.py index ba7911ea9c..ec4d416a8c 100644 --- a/care/facility/api/serializers/shifting.py +++ b/care/facility/api/serializers/shifting.py @@ -24,8 +24,13 @@ ) from care.facility.models.bed import ConsultationBed from care.facility.models.notification import Notification -from care.facility.models.patient_base import NewDischargeReasonEnum +from care.facility.models.patient_base import ( + DISEASE_STATUS_CHOICES, + DiseaseStatusEnum, + NewDischargeReasonEnum, +) from care.facility.models.patient_consultation import PatientConsultation +from care.users.api.serializers.lsg import StateSerializer from care.users.api.serializers.user import UserBaseMinimumSerializer from care.utils.notification_handler import NotificationGenerator from care.utils.serializer.external_id_field import ExternalIdSerializerField @@ -436,7 +441,122 @@ class Meta: read_only_fields = TIMESTAMP_FIELDS -class ShiftingRequestCommentSerializer(serializers.ModelSerializer): +class FacilityShiftingBareMinimumSerializer(serializers.ModelSerializer): + class Meta: + model = Facility + fields = ["id", "name"] + + +class PatientShiftingBareMinimumSerializer(serializers.ModelSerializer): + id = serializers.CharField(source="external_id", read_only=True) + facility = serializers.UUIDField( + source="facility.external_id", allow_null=True, read_only=True + ) + facility_object = FacilityShiftingBareMinimumSerializer( + source="facility", read_only=True + ) + state_object = StateSerializer(source="state", read_only=True) + disease_status = ChoiceField( + choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + ) + age = serializers.SerializerMethodField() + + def get_age(self, obj): + return obj.get_age() + + class Meta: + model = PatientRegistration + fields = [ + "id", + "name", + "allow_transfer", + "age", + "phone_number", + "address", + "disease_status", + "facility", + "facility_object", + "state_object", + ] + read_only = TIMESTAMP_FIELDS + + +class ShiftingListSerializer(serializers.ModelSerializer): + id = serializers.UUIDField(source="external_id", read_only=True) + + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all(), + allow_null=False, + required=True, + ) + patient_object = PatientShiftingBareMinimumSerializer( + source="patient", read_only=True + ) + + status = ChoiceField(choices=SHIFTING_STATUS_CHOICES) + origin_facility_object = FacilityShiftingBareMinimumSerializer( + source="origin_facility", read_only=True + ) + shifting_approving_facility_object = FacilityShiftingBareMinimumSerializer( + source="shifting_approving_facility", read_only=True + ) + + assigned_facility = ExternalIdSerializerField( + queryset=Facility.objects.all(), allow_null=True, required=False + ) + assigned_facility_external = serializers.CharField( + required=False, allow_null=True, allow_blank=True + ) + assigned_facility_object = FacilityShiftingBareMinimumSerializer( + source="assigned_facility", read_only=True + ) + + class Meta: + model = ShiftingRequest + exclude = [ + "created_by", + "last_edited_by", + "assigned_to", + "shifting_approving_facility", + "origin_facility", + "ambulance_number", + "ambulance_phone_number", + "ambulance_driver_name", + "is_assigned_to_user", + "is_kasp", + "refering_facility_contact_number", + "refering_facility_contact_name", + "comments", + "preferred_vehicle_choice", + "vehicle_preference", + "reason", + "is_up_shift", + "assigned_facility_type", + "deleted", + "breathlessness_level", + ] + read_only_fields = TIMESTAMP_FIELDS + + +class ShiftingUserBareMinimumSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["id", "first_name", "last_name"] + + +class ShiftingRequestCommentListSerializer(serializers.ModelSerializer): + id = serializers.UUIDField(source="external_id", read_only=True) + comment = serializers.CharField(required=True) + created_by_object = ShiftingUserBareMinimumSerializer( + source="created_by", read_only=True + ) + + class Meta: + model = ShiftingRequestComment + fields = ["id", "comment", "modified_date", "created_by_object"] + + +class ShiftingRequestCommentDetailSerializer(ShiftingRequestCommentListSerializer): id = serializers.UUIDField(source="external_id", read_only=True) created_by_object = UserBaseMinimumSerializer(source="created_by", read_only=True) diff --git a/care/facility/api/viewsets/shifting.py b/care/facility/api/viewsets/shifting.py index 3d87f822fd..3111e70c30 100644 --- a/care/facility/api/viewsets/shifting.py +++ b/care/facility/api/viewsets/shifting.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.db.models.query import QuerySet from django.db.models.query_utils import Q from django.utils.timezone import localtime, now from django_filters import rest_framework as filters @@ -15,7 +16,9 @@ from care.facility.api.serializers.shifting import ( ShiftingDetailSerializer, - ShiftingRequestCommentSerializer, + ShiftingListSerializer, + ShiftingRequestCommentDetailSerializer, + ShiftingRequestCommentListSerializer, ShiftingSerializer, has_facility_permission, ) @@ -93,36 +96,7 @@ class ShiftingViewSet( ): serializer_class = ShiftingSerializer lookup_field = "external_id" - queryset = ShiftingRequest.objects.all().select_related( - "origin_facility", - "origin_facility__ward", - "origin_facility__local_body", - "origin_facility__district", - "origin_facility__state", - "shifting_approving_facility", - "shifting_approving_facility__ward", - "shifting_approving_facility__local_body", - "shifting_approving_facility__district", - "shifting_approving_facility__state", - "assigned_facility", - "assigned_facility__ward", - "assigned_facility__local_body", - "assigned_facility__district", - "assigned_facility__state", - "patient", - "patient__ward", - "patient__local_body", - "patient__district", - "patient__state", - "patient__facility", - "patient__facility__ward", - "patient__facility__local_body", - "patient__facility__district", - "patient__facility__state", - "assigned_to", - "created_by", - "last_edited_by", - ) + queryset = ShiftingRequest.objects.all() ordering_fields = ["id", "created_date", "modified_date", "emergency"] permission_classes = (IsAuthenticated, DRYPermissions) @@ -133,8 +107,52 @@ class ShiftingViewSet( ) filterset_class = ShiftingFilterSet + def get_queryset(self) -> QuerySet: + if self.action == "list": + self.queryset = self.queryset.select_related( + "origin_facility", + "shifting_approving_facility", + "assigned_facility", + "patient", + ) + + else: + self.queryset = self.queryset.select_related( + "origin_facility", + "origin_facility__ward", + "origin_facility__local_body", + "origin_facility__district", + "origin_facility__state", + "shifting_approving_facility", + "shifting_approving_facility__ward", + "shifting_approving_facility__local_body", + "shifting_approving_facility__district", + "shifting_approving_facility__state", + "assigned_facility", + "assigned_facility__ward", + "assigned_facility__local_body", + "assigned_facility__district", + "assigned_facility__state", + "patient", + "patient__ward", + "patient__local_body", + "patient__district", + "patient__state", + "patient__facility", + "patient__facility__ward", + "patient__facility__local_body", + "patient__facility__district", + "patient__facility__state", + "assigned_to", + "created_by", + "last_edited_by", + ) + return self.queryset + def get_serializer_class(self): serializer_class = self.serializer_class + if self.action == "list": + return ShiftingListSerializer if self.action == "retrieve": serializer_class = ShiftingDetailSerializer return serializer_class @@ -186,7 +204,8 @@ def list(self, request, *args, **kwargs): field_header_map=ShiftingRequest.CSV_MAPPING, field_serializer_map=ShiftingRequest.CSV_MAKE_PRETTY, ) - return super(ShiftingViewSet, self).list(request, *args, **kwargs) + response = super().list(request, *args, **kwargs) + return response class ShifitngRequestCommentViewSet( @@ -195,7 +214,7 @@ class ShifitngRequestCommentViewSet( mixins.RetrieveModelMixin, GenericViewSet, ): - serializer_class = ShiftingRequestCommentSerializer + serializer_class = ShiftingRequestCommentDetailSerializer lookup_field = "external_id" queryset = ShiftingRequestComment.objects.all().order_by("-created_date") @@ -243,3 +262,8 @@ def get_request(self): def perform_create(self, serializer): serializer.save(request=self.get_request()) + + def get_serializer_class(self): + if self.action == "list": + return ShiftingRequestCommentListSerializer + return ShiftingRequestCommentDetailSerializer diff --git a/care/facility/tests/test_facility_shifting_api.py b/care/facility/tests/test_facility_shifting_api.py new file mode 100644 index 0000000000..2a849e3f4e --- /dev/null +++ b/care/facility/tests/test_facility_shifting_api.py @@ -0,0 +1,222 @@ +from enum import Enum + +from rest_framework import status +from rest_framework.test import APITestCase +from rest_framework_simplejwt.tokens import RefreshToken + +from care.utils.tests.test_utils import TestUtils + + +class ExpectedShiftListKeys(Enum): + id = "id" + patient = "patient" + patient_object = "patient_object" + status = "status" + origin_facility_object = "origin_facility_object" + shifting_approving_facility_object = "shifting_approving_facility_object" + assigned_facility = "assigned_facility" + assigned_facility_external = "assigned_facility_external" + assigned_facility_object = "assigned_facility_object" + external_id = "external_id" + created_date = "created_date" + modified_date = "modified_date" + emergency = "emergency" + + +class PatientObjectKeys(Enum): + id = "id" + name = "name" + allow_transfer = "allow_transfer" + age = "age" + phone_number = "phone_number" + address = "address" + disease_status = "disease_status" + facility = "facility" + facility_object = "facility_object" + state_object = "state_object" + + +class FacilityKeys(Enum): + id = "id" + name = "name" + + +class StateKeys(Enum): + id = "id" + name = "name" + + +class ExpectedShiftRetrieveKeys(Enum): + ID = "id" + PATIENT = "patient" + PATIENT_OBJECT = "patient_object" + STATUS = "status" + BREATHLESSNESS_LEVEL = "breathlessness_level" + ORIGIN_FACILITY = "origin_facility" + ORIGIN_FACILITY_OBJECT = "origin_facility_object" + SHIFTING_APPROVING_FACILITY = "shifting_approving_facility" + SHIFTING_APPROVING_FACILITY_OBJECT = "shifting_approving_facility_object" + ASSIGNED_FACILITY = "assigned_facility" + ASSIGNED_FACILITY_EXTERNAL = "assigned_facility_external" + ASSIGNED_FACILITY_OBJECT = "assigned_facility_object" + ASSIGNED_FACILITY_TYPE = "assigned_facility_type" + PREFERRED_VEHICLE_CHOICE = "preferred_vehicle_choice" + ASSIGNED_TO_OBJECT = "assigned_to_object" + CREATED_BY_OBJECT = "created_by_object" + LAST_EDITED_BY_OBJECT = "last_edited_by_object" + AMBULANCE_DRIVER_NAME = "ambulance_driver_name" + AMBULANCE_NUMBER = "ambulance_number" + EXTERNAL_ID = "external_id" + CREATED_DATE = "created_date" + MODIFIED_DATE = "modified_date" + EMERGENCY = "emergency" + IS_UP_SHIFT = "is_up_shift" + REASON = "reason" + VEHICLE_PREFERENCE = "vehicle_preference" + COMMENTS = "comments" + REFERING_FACILITY_CONTACT_NAME = "refering_facility_contact_name" + REFERING_FACILITY_CONTACT_NUMBER = "refering_facility_contact_number" + IS_KASP = "is_kasp" + IS_ASSIGNED_TO_USER = "is_assigned_to_user" + AMBULANCE_PHONE_NUMBER = "ambulance_phone_number" + ASSIGNED_TO = "assigned_to" + CREATED_BY = "created_by" + LAST_EDITED_BY = "last_edited_by" + + +class ExpectedShiftCommentListCreatedByKeys(Enum): + ID = "id" + FIRST_NAME = "first_name" + LAST_NAME = "last_name" + + +class ExpectedShiftCommentListKeys(Enum): + ID = "id" + COMMENT = "comment" + MODIFIED_DATE = "modified_date" + CREATED_BY_OBJECT = "created_by_object" + + +class ExpectedShiftCommentRetrieveCreatedByKeys(Enum): + ID = "id" + FIRST_NAME = "first_name" + LAST_NAME = "last_name" + USERNAME = "username" + EMAIL = "email" + USER_TYPE = "user_type" + LAST_LOGIN = "last_login" + + +class ExpectedShiftCommentRetrieveKeys(Enum): + ID = "id" + COMMENT = "comment" + CREATED_BY_OBJECT = "created_by_object" + EXTERNAL_ID = "external_id" + CREATED_DATE = "created_date" + MODIFIED_DATE = "modified_date" + CREATED_BY = "created_by" + + +class ShiftingViewSetTestCase(TestUtils, APITestCase): + @classmethod + def setUpTestData(cls): + cls.state = cls.create_state() + cls.district = cls.create_district(cls.state) + cls.local_body = cls.create_local_body(cls.district) + cls.user = cls.create_user( + username="test_user", district=cls.district, local_body=cls.local_body + ) + cls.facility = cls.create_facility( + user=cls.user, district=cls.district, local_body=cls.local_body + ) + cls.patient = cls.create_patient(cls.district, cls.facility) + cls.patient_shift = cls.create_patient_shift( + facility=cls.facility, user=cls.user, patient=cls.patient + ) + + def setUp(self) -> None: + refresh_token = RefreshToken.for_user(self.user) + self.client.credentials( + HTTP_AUTHORIZATION=f"Bearer {refresh_token.access_token}" + ) + + def test_list_shift(self): + response = self.client.get("/api/v1/shift/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsInstance(response.json()["results"], list) + + expected_keys = [key.value for key in ExpectedShiftListKeys] + data = response.json()["results"][0] + self.assertCountEqual(data.keys(), expected_keys) + + def test_retrieve_shift(self): + response = self.client.get(f"/api/v1/shift/{self.patient_shift.external_id}/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + expected_keys = [key.value for key in ExpectedShiftRetrieveKeys] + data = response.json() + self.assertCountEqual(data.keys(), expected_keys) + + +class ShifitngRequestCommentViewSetTestCase(TestUtils, APITestCase): + @classmethod + def setUpTestData(cls) -> None: + cls.state = cls.create_state() + cls.district = cls.create_district(cls.state) + cls.local_body = cls.create_local_body(cls.district) + cls.user = cls.create_user( + username="test_user", district=cls.district, local_body=cls.local_body + ) + cls.facility = cls.create_facility( + user=cls.user, district=cls.district, local_body=cls.local_body + ) + cls.patient = cls.create_patient(cls.district, cls.facility) + cls.patient_shift = cls.create_patient_shift( + facility=cls.facility, user=cls.user, patient=cls.patient + ) + cls.patient_shift_comment = cls.create_patient_shift_comment( + resource=cls.patient_shift + ) + + def setUp(self) -> None: + refresh_token = RefreshToken.for_user(self.user) + self.client.credentials( + HTTP_AUTHORIZATION=f"Bearer {refresh_token.access_token}" + ) + + def test_list_shift_request_comment(self): + response = self.client.get( + f"/api/v1/shift/{self.patient_shift.external_id}/comment/" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIsInstance(response.json()["results"], list) + + expected_keys = [key.value for key in ExpectedShiftCommentListKeys] + data = response.json()["results"][0] + self.assertCountEqual(data.keys(), expected_keys) + + expected_created_by_keys = [ + key.value for key in ExpectedShiftCommentListCreatedByKeys + ] + data = response.json()["results"][0]["created_by_object"] + if data: + self.assertCountEqual(data.keys(), expected_created_by_keys) + + def test_retrieve_shift_request_comment(self): + response = self.client.get( + f"/api/v1/shift/{self.patient_shift.external_id}/comment/{self.patient_shift_comment.external_id}/" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + expected_keys = [key.value for key in ExpectedShiftCommentRetrieveKeys] + data = response.json() + self.assertCountEqual(data.keys(), expected_keys) + + expected_created_by_keys = [ + key.value for key in ExpectedShiftCommentRetrieveCreatedByKeys + ] + data = response.json()["created_by_object"] + if data: + self.assertCountEqual(data.keys(), expected_created_by_keys) diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index e396b9a51e..dd5bd07477 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -6,11 +6,17 @@ from django.test import override_settings from django.utils.timezone import make_aware, now +from faker import Faker from rest_framework import status +from rest_framework_simplejwt.tokens import RefreshToken from care.facility.models import ( + BREATHLESSNESS_CHOICES, CATEGORY_CHOICES, DISEASE_CHOICES_MAP, + FACILITY_TYPES, + SHIFTING_STATUS_CHOICES, + VEHICLE_CHOICES, Ambulance, Disease, DiseaseStatusEnum, @@ -19,6 +25,8 @@ PatientConsultation, PatientExternalTest, PatientRegistration, + ShiftingRequest, + ShiftingRequestComment, User, Ward, ) @@ -38,6 +46,8 @@ ) from care.users.models import District, State +fake = Faker() + class override_cache(override_settings): """ @@ -615,3 +625,74 @@ def get_facility_representation(self, facility): }, **self.get_local_body_district_state_representation(facility), } + + def create_patient_note( + self, patient=None, note="Patient is doing find", created_by=None, **kwargs + ): + data = { + "facility": patient.facility or self.facility, + "note": note, + } + data.update(kwargs) + patientId = patient.external_id + + refresh_token = RefreshToken.for_user(created_by) + self.client.credentials( + HTTP_AUTHORIZATION=f"Bearer {refresh_token.access_token}" + ) + + self.client.post(f"/api/v1/patient/{patientId}/notes/", data=data) + + @classmethod + def create_patient_shift( + cls, + facility: Facility = None, + user: User = None, + patient: PatientRegistration = None, + **kwargs, + ) -> None: + shifting_approving_facility = cls.create_facility( + user=cls.user, district=cls.district, local_body=cls.local_body + ) + assigned_facility = shifting_approving_facility + data = { + "origin_facility": assigned_facility, + "shifting_approving_facility": shifting_approving_facility, + "assigned_facility_type": fake.random_element(FACILITY_TYPES)[0], + "assigned_facility": assigned_facility, + "assigned_facility_external": "Assigned Facility External", + "patient": patient, + "emergency": False, + "is_up_shift": False, + "reason": "Reason", + "vehicle_preference": "Vehicle Preference", + "preferred_vehicle_choice": fake.random_element(VEHICLE_CHOICES)[0], + "comments": "Comments", + "refering_facility_contact_name": "9900199001", + "refering_facility_contact_number": "9900199001", + "is_kasp": False, + "status": fake.random_element(SHIFTING_STATUS_CHOICES)[0], + "breathlessness_level": fake.random_element(BREATHLESSNESS_CHOICES)[0], + "is_assigned_to_user": False, + "assigned_to": user, + "ambulance_driver_name": fake.name(), + "ambulance_phone_number": "9900199001", + "ambulance_number": fake.license_plate(), + "created_by": user, + "last_edited_by": user, + } + data.update(kwargs) + return ShiftingRequest.objects.create(**data) + + @classmethod + def create_patient_shift_comment( + cls, resource: ShiftingRequest = None, user: User = None, **kwargs + ) -> None: + kwargs.update( + { + "request": resource, + "comment": "comment", + "created_by": user, + } + ) + return ShiftingRequestComment.objects.create(**kwargs)